题目3180:蓝桥杯2023年第十四届省赛真题-棋盘
https://www.dotcpp.com/oj/problem3180.html
package Dotcpp;
import java.util.Scanner;
public class 题目3180蓝桥杯2023年第十四届省赛真题__棋盘 {
/*
小蓝拥有 n × n 大小的棋盘
小蓝进行了 m 次操作
每次操作会将棋盘上某个范围内的所有棋子的颜色取反 (也就是白色棋子变为黑色,黑色棋子变为白色)。
*/
public static void main(String[] args) {
//输入的第一行包含两个整数 n, m,
//接下来 m 行每行包含四个整数 x1, y1, x2, y2,
//对于所有评测用例,1 ≤ n, m ≤ 2000 ,1 ≤ x1 ≤ x2 ≤ n ,1 ≤ y1 ≤ y2 ≤ m 。
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int qipan [][] = new int[n][n];
while(m-->0){
int x1 = sc.nextInt();
int y1 = sc.nextInt();
int x2 = sc.nextInt();
int y2 = sc.nextInt();
Reverse(qipan,x1,y1,x2,y2);
}//while(m-->=0)
for (int i=0;i<n;i++){
for (int j=0;j<n;j++){
System.out.print(qipan[i][j]);
}
System.out.println();
}
}
private static void Reverse(int[][] qipan, int x1, int y1, int x2, int y2) {
for (int i=x1-1;i<x2;i++){
for (int j=y1-1;j<y2;j++){
if (qipan[i][j] == 0){
qipan[i][j] = 1;
}else{
qipan[i][j] = 0;
}
}
}
}
}
必然会造成超时问题
因此可以看到,该思路完全正确,但是会面临超时问题,必然不是一个很好的解决方案
private static void Reverse(int[][] qipan, int x1, int y1, int x2, int y2) {
for (int i=x1-1;i<x2;i++){
for (int j=y1-1;j<y2;j++){
if (qipan[i][j] == 0){
qipan[i][j] = 1;
}else{
qipan[i][j] = 0;
}
}
}
核心代码如下,大家可以看出,此思路很简单,就是:
- 构造一个默认的数组,默认值都是0,刚刚好符合白色的概念
- 进行循环调整,遇到1则变成0,遇到0则变成1
package Dotcpp;
import java.util.Scanner;
public class 题目3180蓝桥杯2023年第十四届省赛真题__棋盘_AC36 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int qipan [][] = new int[2002][2002];
while (m-->0){
int x1 = sc.nextInt();
int y1 = sc.nextInt();
int x2 = sc.nextInt();
int y2 = sc.nextInt();
for (int i=x1;i<=x2;i++){
qipan[i][y1]++;
qipan[i][y2+1]--;
}
}
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
qipan[i][j]+=qipan[i][j-1];
System.out.print(qipan[i][j]%2);
}
System.out.println();
}
}
}
将整个棋盘拆开,每次x1,x2依然判断,但y1-y2不进行判断,而只是标记出起始位置
然后最后在通过累加,减少来判断取0/1.
这个方法很巧妙,核心代码如上图圈出来的部分,
主要改进点如下:
1.不再对y1到y2进行遍历,将三重循环,改为二重循环
2.记录下每次变遍历的头y1,和结尾的下一个,即y2+1 ,然后对他们进行操作,方便后续的判别
3.最后就是qipan[i][j]+=qipan[i][j-1]; ,即进行累加求和,然后取模进行判断
照理说,这个代码应该已经可以通过了,但是最后的结果却和第一次提交的结果完全一致。
当时这里已经心态爆炸,百思不得其解啊。
import java.io.*;
import java.util.Arrays;
import java.util.Scanner;
/**
* ClassName: F棋盘
* Package: com.蓝桥真题._14届
* Describe:
3 3
1 1 2 2
2 2 3 3
1 1 3 3
ans:
001
010
100
* @Create 2023/04/10 10:49
*/
public class Main {
static int n,m;static int[][] map;
public static void main(String[] args) throws Exception {
n = nextInt(); m = nextInt(); map = new int[n + 4][n + 4];
for(int [] row : map) Arrays.fill(row,1);
//show();
for(int i = 0; i < m; i++){ insert(nextInt(),nextInt(),nextInt(),nextInt(),-1); }
//pw.println("---------------------"); show();
for(int i =1; i <= n; i++){
for(int j = 1; j <= n; j++){
map[i][j] *= map[i - 1][j] * map[i][j-1] / map[i - 1][j - 1];
pw.print(map[i][j] == 1 ? 0 : 1);
}
pw.println();
}
pw.flush();
}
static void insert(int x1,int y1,int x2,int y2,int q){
map[x1][y1] *= q;
map[x2 + 1 ][y1] /= q;
map[x1][y2 +1 ] /= q;
map[x2 + 1][y2 + 1] *= q;
}
static void show(){
for(int[] row : map){
for(int col : row)pw.printf("%3d",col);
pw.println();
}
}
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StreamTokenizer st = new StreamTokenizer(br);
static int nextInt() throws Exception {st.nextToken();return (int) st.nval;}
static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
}
此方法仔细观察后,大家可以发现,其实相比上面的第一种和第二种造成了更多的冗余,且已经把数字由原来的0-1判断,改成了1和-1的判断,最后在通过
pw.print(map[i][j] == 1 ? 0 : 1);
这条语句把它改回判断
//package Dotcpp;
import java.util.Arrays;
import java.util.Scanner;
public class Main {
private static int n,m;
private static int qipan [][] ;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
qipan = new int[n+2][n+2];
for (int row [] : qipan){
Arrays.fill(row,1);
}
while (m-->0){
int x1 = sc.nextInt();
int y1 = sc.nextInt();
int x2 = sc.nextInt();
int y2 = sc.nextInt();
myInsert(x1,y1,x2,y2,-1,qipan);
}
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
qipan[i][j]*=qipan[i][j-1]*qipan[i][j-1]/qipan[i-1][j-1];
System.out.print(qipan[i][j] == 1 ? 0:1);
}
System.out.println();
}
sc.close();
}
private static void myInsert(int x1, int y1, int x2, int y2, int q, int[][] qipan) {
qipan[x1][y1]*=q;
qipan[x2+1][y1] /=q;
qipan[x1][y2+1] /=q;
qipan[x2+1][y2+1] *=q;
}
}
这一次只是单纯地把别人的实现代码给copy过来,套在自己的框架里,
当时感觉很奇怪,分析了一下,感觉还没有上述第二次尝试的设计思路好
要是细心地小伙伴可以自己多研究一下,就能看出来,有一个地方代码写错了,这里我就调皮一下,不圈出来,因为也不是本文的重点。
//package Dotcpp;
import java.io.*;
import java.util.Arrays;
import java.util.Scanner;
public class Main {
private static int n,m;
private static int qipan [][] ;
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StreamTokenizer st = new StreamTokenizer(br);
static int nextInt() throws Exception {st.nextToken();return (int) st.nval;}
static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) throws Exception {
//Scanner sc = new Scanner(System.in);
// n = sc.nextInt();
// m = sc.nextInt();
n = nextInt();
m = nextInt();
qipan = new int[n+2][n+2];
for (int row [] : qipan){
Arrays.fill(row,1);
}
while (m-->0){
/*
int x1 = sc.nextInt();
int y1 = sc.nextInt();
int x2 = sc.nextInt();
int y2 = sc.nextInt();
*/
int x1 = nextInt();
int y1 = nextInt();
int x2 = nextInt();
int y2 = nextInt();
myInsert(x1,y1,x2,y2,-1);
}
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
qipan[i][j]*=qipan[i-1][j]*qipan[i][j-1]/qipan[i-1][j-1];
System.out.print(qipan[i][j] == 1 ? 0:1);
}
System.out.println();
}
//sc.close();
}
private static void myInsert(int x1, int y1, int x2, int y2, int q) {
qipan[x1][y1]*=q;
qipan[x2+1][y1] /=q;
qipan[x1][y2+1] /=q;
qipan[x2+1][y2+1] *=q;
}
}
1.引入了本文的另一个核心讨论板块:
2.改正了一下上面提到的一个小错误
//package Dotcpp;
import java.io.*;
import java.util.Arrays;
import java.util.Scanner;
public class Main {
private static int n,m;
private static int qipan [][] ;
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StreamTokenizer st = new StreamTokenizer(br);
static int nextInt() throws Exception {st.nextToken();return (int) st.nval;}
static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) throws Exception {
//Scanner sc = new Scanner(System.in);
// n = sc.nextInt();
// m = sc.nextInt();
n = nextInt();
m = nextInt();
qipan = new int[n+2][n+2];
for (int row [] : qipan){
Arrays.fill(row,1);
}
while (m-->0){
/*
int x1 = sc.nextInt();
int y1 = sc.nextInt();
int x2 = sc.nextInt();
int y2 = sc.nextInt();
*/
int x1 = nextInt();
int y1 = nextInt();
int x2 = nextInt();
int y2 = nextInt();
myInsert(x1,y1,x2,y2,-1);
}
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
qipan[i][j]*=qipan[i-1][j]*qipan[i][j-1]/qipan[i-1][j-1];
//System.out.print(qipan[i][j] == 1 ? 0:1);
pw.print(qipan[i][j] == 1 ? 0:1);
}
//System.out.println();
pw.println();
}
pw.flush();
//sc.close();
}
private static void myInsert(int x1, int y1, int x2, int y2, int q) {
qipan[x1][y1]*=q;
qipan[x2+1][y1] /=q;
qipan[x1][y2+1] /=q;
qipan[x2+1][y2+1] *=q;
}
}
可以看到,全部AC
通过这道题,大家可以深切的感受到流式输入的影响,在此我先贴出上述代码的输入格式,然后给出其他任意类型的输入形式,供大家以后使用
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StreamTokenizer st = new StreamTokenizer(br);
static int nextInt() throws Exception {st.nextToken();return (int) st.nval;}
static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
于是我对之间的第一次尝试和第二次尝试也改用文件流式输入。
//package Dotcpp;
import java.io.*;
import java.util.Scanner;
public class Main {
/*
小蓝拥有 n × n 大小的棋盘
小蓝进行了 m 次操作
每次操作会将棋盘上某个范围内的所有棋子的颜色取反 (也就是白色棋子变为黑色,黑色棋子变为白色)。
*/
private static int n,m;
private static int qipan [][] ;
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StreamTokenizer st = new StreamTokenizer(br);
static int nextInt() throws Exception {st.nextToken();return (int) st.nval;}
static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) throws Exception {
//输入的第一行包含两个整数 n, m,
//接下来 m 行每行包含四个整数 x1, y1, x2, y2,
//对于所有评测用例,1 ≤ n, m ≤ 2000 ,1 ≤ x1 ≤ x2 ≤ n ,1 ≤ y1 ≤ y2 ≤ m 。
n = nextInt();
m = nextInt();
qipan = new int[n+4][n+4];
//int qipan [][] = new int[n][n];
while(m-->0){
// int x1 = sc.nextInt();
// int y1 = sc.nextInt();
// int x2 = sc.nextInt();
// int y2 = sc.nextInt();
int x1 = nextInt();
int y1 = nextInt();
int x2 = nextInt();
int y2 = nextInt();
Reverse(x1,y1,x2,y2);
}//while(m-->=0)
for (int i=0;i<n;i++){
for (int j=0;j<n;j++){
//System.out.print(qipan[i][j]);
pw.print(qipan[i][j]);
}
//System.out.println();
pw.println();
}
pw.flush();
}
private static void Reverse(int x1, int y1, int x2, int y2) {
for (int i=x1-1;i<x2;i++){
for (int j=y1-1;j<y2;j++){
if (qipan[i][j] == 0){
qipan[i][j] = 1;
}else{
qipan[i][j] = 0;
}
}
}
}
}
//package Dotcpp;
import java.io.*;
import java.util.Scanner;
public class Main {
private static int n,m;
private static int qipan [][] ;
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StreamTokenizer st = new StreamTokenizer(br);
static int nextInt() throws Exception {st.nextToken();return (int) st.nval;}
static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) throws Exception {
// Scanner sc = new Scanner(System.in);
// int n = sc.nextInt();
// int m = sc.nextInt();
n = nextInt();
m = nextInt();
int qipan [][] = new int[n+4][n+4];
while (m-->0){
// int x1 = sc.nextInt();
// int y1 = sc.nextInt();
// int x2 = sc.nextInt();
// int y2 = sc.nextInt();
int x1 = nextInt();
int y1 = nextInt();
int x2 = nextInt();
int y2 = nextInt();
for (int i=x1;i<=x2;i++){
qipan[i][y1]++;
qipan[i][y2+1]--;
}
}
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
qipan[i][j]+=qipan[i][j-1];
//System.out.print(qipan[i][j]%2);
pw.print(qipan[i][j]%2);
}
pw.println();
//System.out.println();
}
pw.flush();
}
}
可以看到,全部AC!!!
因此我们下面探讨一下Scanner和文件流输入输出到底为什么差别这么大!
概述:JDK1.5后用于扩区用户的键盘输入;
原理: Scanner(InputStream source)(构造一个新的Scanner,他声称的值是指定的输入流扫描的;)
System类下面有一个静态的字段:public static fianl Inputstream in;(标准的输入流,对应着键盘录入)
即:Inputstream in=Sysytem.in;
Scanner scanner=new Scanner(in);
两套录入方法:
1:录入数据:nextxxx(Scanner.nextLong)(Scanner.nextDouble)...
2:录入字符串:nextLine()(String s=scanner.nextLine()
PS:使用nextLine()方法时,你先录入整数,在录入字符串时,录入不进去,需要在重新创建一个Scanner对象。
hasnextXXX():判断录入数据的元素。
大家可以看到Scanner的本质是对用户键盘上的输入,进行一个一个的扫描。
1. IO流介绍
IO流(Input Output Stream,输入输出流),表示数据在程序内存和磁盘之间的传输。按照数据流的流向不同分为输入、输出流,输入流表示程序从磁盘读入数据,输出流表示程序往磁盘写数据。按照数据读取、写的方式不同分为字节流和字符流,字节流(类名以Stream结尾)表示程序按字节读取数据,什么文件都可以读取;字符流表示程序按照字符方式读取,方便读取各种编码的文本文件,但是无法读取图片、音频及视频等文件。所有的流都实现了java.io.Closeable接口,都有close方法。所有的输出流都实现了java.io.Flushable接口,都有flush方法,字符流需要手动使用flush方法才会把数据写入磁盘。
————————————————
版权声明:本文为CSDN博主「原来的1024」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yldmkx/article/details/116676428
IO流自然不用多说,粗暴点认为就是一次性读取全部的,整个文件里面的数据
Java StringTokenizer 属于 java.util 包,用于分隔字符串!
StringTokenizer(String str) :构造一个用来解析 str 的 StringTokenizer 对象。java 默认的分隔符是空格(“”)、制表符(\t)、换行符(\n)、回车符(\r)。
StringTokenizer(String str, String delim) :构造一个用来解析 str 的 StringTokenizer 对象,并提供一个指定的分隔符。
StringTokenizer(String str, String delim, boolean returnDelims) :构造一个用来解析 str 的 StringTokenizer 对象,并提供一个指定的分隔符,同时,指定是否返回分隔符。
以后大家遇到超时问题,也可以多考虑一下输入文件流:
老样子,先贴上本文的代码:
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StreamTokenizer st = new StreamTokenizer(br);
static int nextInt() throws Exception {st.nextToken();return (int) st.nval;}
static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
更多类型的文件输入输出流显示:
输入:
private static char getChar() throws IOException {
String s = getString();
return s.charAt(0);
}
private static String getString() throws IOException {
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = bufferedReader.readLine();
return str;
}
private static int getInt() throws IOException {
String s = getString();
return Integer.parseInt(s);
输出:
private static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));