Description
是这样的,在小杉的面前有一个N行M列的棋盘,棋盘上有N*M个有黑白棋的棋子(一面为黑,一面为白),一开始都是白面朝上。
小杉可以对任意一个格子进行至多一次的操作(最多进行N*M个操作),该操作使得与该格同列的上下各2个格子以及与该格同行的左右各1个格子以及该格子本身翻面。
例如,对于一个5*5的棋盘,仅对第三行第三列的格子进行该操作,得到如下棋盘(0表示白面向上,1表示黑面向上)。00100
00100
01110
00100
00100对一个棋盘进行适当的操作,使得初始棋盘(都是白面朝上)变成已给出的目标棋盘的操作集合称作一个解法。
小杉的任务是对给出的目标棋盘求出所有解法的总数。
(N,M<=20,T<=5 2s)
Solution
先认真分析下题目
对一个点进行操作,他只对左右各一列,上下两列有影响。
又因为操作顺序对结果没有影响(显然)
所以可以考虑顺序从左到右搜下来
可以发现,上一列的状态是完全影响这一列的状态的
00100
00100
01110
00100
00100
假设当前搜到中间那个1,那么现在就必须进行操作,因为再往后已经没有其他的操作可以改变上一个1的状态了。也就是说,只要一个点左边的是1,那么这个点一定要进行操作。
所以完全可以枚举第0列(也就相当于枚举第1行如何操作,然后做一遍下来就好了,判断最后一行是否全为0,若是则累计入答案。
复杂度O(NMT*2^N)
这还有不小的常数,是过不了的。
考虑优化
我们知道,0 xor 1=1,1 xor 1=0
是不是很像我们的操作?
设上一列二进制状态为y1,当前列状态为y2
y2 xor y1,你想到了什么?
看看样例
00100
00100
01110
00100
00100
当前做到第3列,状态为y2,上一行状态是y1,y2 xor y1
那么状态就变成
00100
00100
01010
00100
00100
是不是改变了当前要改变的状态?
如果把y1向下拉一格,再xor
00100
00100
01010
00000
00100
你又发现了什么?
再拉一格
00100
00100
01010
00000
00000
如果再往上拉,把上面的状态同时改变
00000
00000
01010
00000
00000
再往右
00000
00000
01000
00000
00000
是不是都完成了?
同时,这样操作是把所有前一列的1都进行一次操作,一次做一整列
复杂度就变成了O(MT*2^N),可以在2s以内完成(平均为1s左右)
Code
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fod(i,a,b) for(i=a;i>=b;i--)
using namespace std;
int a[22],n,m,t;
char c[22][22];
string st;
int main()
{
cin>>n>>m>>t;
scanf("\n");
while (t-->0)
{
int i,j;
memset(a,0,sizeof(a));
fo(i,1,n)
{
fo(j,1,m)
{
cin>>c[i][j];
}
scanf("\n");
}
int v;
fo(j,1,m)
{
v=1;
fo(i,1,n)//我是把整个图顺时针转了90度,更好操作
{
a[j]+=v*(c[i][j]-48);//二进制转换成十进制状态
v*=2;
}
}
int ans=0,k,b[21],al=(1<<n)-1;
fo(a[0],0,al)
{
fo(i,0,m) b[i]=a[i];
fo(i,1,m)
{
b[i]=(b[i]^(b[i-1]*2)^(b[i-1]*4)^(b[i-1]/4)^(b[i-1]/2)^b[i-1])&al; //操作当前的状态
b[i+1]=(b[i+1]^b[i-1])&al;//操作下一状态
}
if ((b[m]&al)==0)
ans++;
}
cout<<ans<<endl;
}
}
核心代码其实只有2行,比一个个暴力修改简洁多了。
其实很简单,不要把这题想的特别复杂而不敢去做,理解了这个就好了。