前言:本文用于介绍解题的过程
作业训练二
编程题
18. 棋盘
【问题描述】
棋盘是指一个行和列编号从1~N的NxN的二进制矩阵,当行号和列号之和为偶数时该矩阵对应位置为黑色的(1),否则为白色的(0)。以下图示为N=1、2、3时的棋盘。

給出一个NxN的二进制矩阵,请找出位于该矩阵内的最大尺寸的完整棋盘,以及最大尺寸棋盘的数量(棋盘可以交叠)。
【输入形式】
每个测试用例的第一行是一个正整数N(1<=N<=2000),表示給定矩阵的行数和列数,接下来的N行描述了这个矩阵:每行有N个字符,既可以是“1”(代表黑块),也可以是“0”(代表白块)。矩阵至少包含一个“1”字符。
【输出形式】
输出最大尺寸棋盘的行列的大小,以及最大棋盘的个数,以空格分隔。
【样例输入】
5
00101
11010
00101
01010
11101
【样例输出】
3 3
【样例说明】
【评分标准】
7.13是我第一次做这道题目的时间,当时我看到这个题,大致思路就是给这个大矩阵切片。
然后一个个判断嘛。
这里直接以每个元素作为左上角的元素来生成相应长度小矩阵(注意边界情况),这样子为什么就可以生成全部的子矩阵呢?我们考虑它的逆否命题,存在这样一个元素生成的矩阵,不属于之前生成的任何一个矩阵,那也就是说,这个矩阵右上角的元素不存在于这个原始矩阵内,这是一个谬论,并且,一个元素不可能是两个长度相同的子矩阵的最左上角元素,因此可以拿每个元素作为左上角的元素来生成相应长度小矩阵。
给大矩阵切片的时间复杂度是一定的,关键是判断的时间复杂度,我先想到了矩阵的性质,因为每个样例都是一个方阵嘛,或许这样的方阵对应的特征向量或许比较特殊?但是它并没有这样的性质,正定二次型也不行,因为它虽然有唯一对应的多项式,但是还不如直接二重循环来的直接。后面我想到了位运算,因为01010这样的二进制数,刚好对应一个整数。那接下来就是找规律了。
|
二进制数 |
长度 |
十进制数 |
|
10101 |
5 |
21 |
|
01010 |
5 |
10 |
|
101010 |
6 |
42 |
|
010101 |
6 |
21 |
从这个表格可以看出,十进制数对应的长度的规律和和这个长度的奇偶和大小有关吧。
如果长度是偶数
偶数行(0,2,4)对应的是2*(pow(4,len/2)-1)/3;
奇数行(1,3,5)对应的是 (pow(4,len/2)-1)/3;
如果长度是奇数
偶数行(0,2,4)对应的是(pow(4,len/2+1)-1)/3;
奇数行(1,3,5)对应的是 2*(pow(4,len/2)-1)/3;
这个找规律看似轻松,但是有很多对应关系要注意,比如第一个偶数是从什么地方开始算起,即长度与十进制数是如何一一对应的,如果选点选错了,就要统一偏移一个单位,这看上去只用小小的修正就可以了,但是到了后面,这样的偏移太多了,加加减减的,剪不断理还乱,不好休整,最好确定一个关系就记下来它们的对应方式,并且测试一遍,所需的时间绝对比之后改代码要少,而且正确率高。其到了后面,错的太多了,自己心烦意乱,也改不了了。我们拿到一个矩阵,只用将它转化成十进制数,然后两两比较就可以了。
我们的判断函数大致有思路了,接下来是如何把矩阵切片,这又延伸出了一个问题,就是我们怎么去存储这个输入的矩阵,我这里就是直接把矩阵化成十进制数存到一维向量里面,这样节约空间。
如果我们的矩阵是这样
00101
11010
00101
01010
11101
我们想要它的一部分,要这样的
101
010
101
有一个简单的思路,就是搞一个蒙版来进行与运算。
00111
00111
00111
00000
00000
把它和原矩阵做与运算就可以得到所求的矩阵了。这种操作就是位掩码。、
因为我们是存的一维向量,所以说只能一行行做与运算,现在问题来了,如何生成位掩码呢?
比如我想要11010里面的101,我要生成一个01110的位掩码,我们可以这样做:
生成10000,这个简单,就是2的5次幂,然后-1,变成01111,之后右移1位,再左移1位,那个最后的0就出来了,我们就这样生成了位掩码01110。
其实这里同样有很多的对应问题,比如移位和二进制位数索引都有什么关系,这是我们需要一步步确定的,这里推荐开一个新的cpp文档去调试,得出对应关系以后,把这一部分代码拖进来,这样可以确保正确性。
这样子,代码框架差不多出来了,可是交了一遍,发现超时了,那就优化,我以前是直接读入的string,那我现在换成char一个个读,然后我再开ios::sync_with_stdio(0);快读,后面的判断也可以剪枝,那对于第一行第一列元素是0的,我们没必要生成了,直接跳过。
这样我们可以得到最后的程序,代码如下:
#include <iostream>
#include <iomanip>
#include <vector>
#include <stack>
#include <unordered_map>
#include <map>
#include <cstring>
#include <cmath>
#include <queue>
#include <list>
#include <unordered_set>
#include <set>
#include <algorithm>
#include <sstream>
using namespace std;
int n;
void bin(int temp,int k) {
int cnt=k;
stack<int> sta;
while(cnt--) {
int t=temp%2;
sta.push(t);
temp=temp/2;
}
while(!sta.empty()) {
cout<<sta.top();
sta.pop();
}
cout<<endl;
}
int bitmask(int temp,int start,int end) { //like a string, split
//eg:
//bin 10101
//index 43210
int t=pow(2,start+1);
t-=1;
t=(t>>end)<<end;
return temp&t;
}
bool check(vector<int>& square,int len) {
if((int)square.size()!=len) {
//bin(square[0],len), bin((square[0]>>(len-1)),len);
//cout<<"------------"<<endl;
return 0;
}
int up=0,down=len-1;
int seq[2]= {1,1};
if(len%2==0) {
seq[0]=2*(pow(4,len/2)-1)/3;
seq[1]=(pow(4,len/2)-1)/3;
square.push_back(square[0]);
down=len;
} else {
seq[0]=(pow(4,len/2+1)-1)/3;
seq[1]=2*(pow(4,len/2)-1)/3;
}
while(up<=down) {
if(!(square[up]==square[down]&&square[up]==seq[up%2])) {
return 0;
}
up++;
down--;
}
return 1;
}
int main() {
ios::sync_with_stdio(0);
vector<int> board;
cin>>n;
for(int i=0; i<n; i++) {
int tem=0,len=n;
for(int j=len-1; j>=0; j--) {
char ch;
cin>>ch;
tem+=(ch-'0')*pow(2,j);
}
//bin(tem,n);
board.push_back(tem);
}
//cout<<endl;
/*for(int j=0;j<n;j++){
bin(bitmask(board[j],3,2),5);
}
cout<<endl;*/
int slen=-1;
for(int len=1; len<=n; len++) { //length
int pos=n-len+1;
int sign=0;
for(int i=0; i<pos; i++) { //cut index
int index=n-i-1;
for(int j=0; j<pos; j++) { //num index
vector<int> square;
for(int k=0; k<len; k++) { //excursion
int bm=bitmask(board[j+k],index,index-len+1)>>(index-len+1);
if(k==0&&bm>>(len-1)==0) {
break;
}
square.push_back(bm);
//bin(bm,len);
}
if(check(square,len)) {
slen=len;
sign=1;
}
}
}
if(sign==0) {
break;
}
}
int pos=n-slen+1,ans=0;
for(int i=0; i<pos; i++) { //cut index
int index=n-i-1;
for(int j=0; j<pos; j++) { //num index
vector<int> square;
for(int k=0; k<slen; k++) { //excursion
int bm=bitmask(board[j+k],index,index-slen+1)>>(index-slen+1);
square.push_back(bm);
//bin(bm,len);
}
if(check(square,slen)) {
ans++;
}
}
}
cout<<slen<<' '<<ans<<endl;
return 0;
}
前面有一些小函数,方便调试,做大模拟可以把不确定的步骤可视化,最好边做,便验证自己的正确性,因为这样调代码不容易出错,可能第一次出成品的时间慢了10分钟,但是省下30分钟调试代码的时间。如果特别有自信,那也可以一次做对,不调试,这种情况比较罕见hhh。
我去网上搜了一下,直接硬做的时间复杂度可能还要低一些,也可以找一些性质优化判断的时间复杂度,这个位运算主要是省空间吧,这道题其实没有很好地体现出位运算的优势,权当练习了。
1653

被折叠的 条评论
为什么被折叠?



