玩诈欺的小杉——异或优化的状压dp
玩诈欺的小杉
是这样的,在小杉的面前有一个N行M列的棋盘,棋盘上有N*M个有黑白棋的棋子(一面为黑,一面为白),一开始都是白面朝上。
小杉可以对任意一个格子进行至多一次的操作(最多进行N*M个操作),该操作使得与该格同列的上下各2个格子以及与该格同行的左右各1个格子以及该格子本身翻面。
例如,对于一个5*5的棋盘,仅对第三行第三列的格子进行该操作,得到如下棋盘(0表示白面向上,1表示黑面向上)。
00100
00100
01110
00100
00100
对一个棋盘进行适当的操作,使得初始棋盘(都是白面朝上)变成已给出的目标棋盘的操作集合称作一个解法。
小杉的任务是对给出的目标棋盘求出所有解法的总数。
每组测试数据的第一行有3个正整数,分别是N和M和T(1<=N,M<=20,1<=T<=5)
接下来T个目标棋盘,每个目标棋盘N行,每行M个整数之前没有空格且非0即1,表示目标棋盘(0表示白面朝上,1表示黑面朝上)
两个目标棋盘之间有一个空行。
特别地,对于30%的数据,有1<=N,M<=15
对每组数据输出T行,每行一个整数,表示能使初始棋盘达到目标棋盘的解法总数
输入:
4 4 2
0010
0010
0111
0010
0010
0110
0111
0010
输出:
1
1
【样例解释】
对于输入的数据,两个目标棋盘各有一种解法
1:
0000
0000
0010
0000
2:
1011
1101
0111
1011
其中1表示对该格进行操作,0表示不操作
分析:
这种类似棋盘的东西其实很容易让我们想到状压,对比两个方向的状压,我们会倾向于选择一列一列地推过去,因为这样问题更加简单。所以我们可以很轻松地和普通的状压一样打出此题,但是显然的时间复杂度O(2nNMT)会超时,此时我们应试着研究转移过程。我们会发现,0与1的翻转其实就是异或的过程,在上述做法中我们一一枚举了每一列的各个数。现在我们来考虑转化为异或来将复杂度缩去一个N,对于"十字架"两端可以直接异或,而对于中间那一长条我们要稍微处理一下,将这一串进行移动再累次异或即可。
代码:
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> using namespace std; #define int long long #define R register #define ld long double #define debug printf("zxt\n") inline int read(){ int a=0,b=1;char c=getchar(); while(!isdigit(c)){if(c=='-')b=-1;c=getchar();} while(isdigit(c)){a=a*10+c-'0';c=getchar();} return a*b; } const int N=50; int n,m,T,a[N],b[N],al,ans; char s[N]; signed main(){ n=read();m=read(); T=read(); while(T--){ for(R int j=1;j<=m;j++){ a[j]=0; } for(R int i=1;i<=n;i++){ scanf("%s",s); for(R int j=1;j<=m;j++){ a[j]=a[j]*2+s[j-1]-'0'; } } al=(1<<n)-1;ans=0; for(a[0]=0;a[0]<=al;a[0]++){ for(R int i=0;i<=m;i++)b[i]=a[i]; for(R int i=1;i<=m;i++){ b[i]=(b[i]^(b[i-1]<<2)^(b[i-1]<<1)^b[i-1]^(b[i-1]>>1)^(b[i-1]>>2))&al; b[i+1]=(b[i-1]^b[i+1])&al; } if((b[m]&al)==0)ans++; } printf("%lld\n",ans); } return 0; }