棋盘 位运算做法

前言:本文用于介绍解题的过程

作业训练二

编程题

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。

我去网上搜了一下,直接硬做的时间复杂度可能还要低一些,也可以找一些性质优化判断的时间复杂度,这个位运算主要是省空间吧,这道题其实没有很好地体现出位运算的优势,权当练习了。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值