紀中暑假集訓 2020.08.10【NOIP提高組】模擬 T3:玩詐欺的小杉
玩詐欺的小杉
Description
是這樣的,在小杉的面前有一個N行M列的棋盤,棋盤上有\(N*M\)個有黑白棋的棋子(一面為黑,一面為白),一開始都是白麵朝上。
小杉可以對任意一個格子進行至多一次的操作(最多進行\(N*M\)個操作),該操作使得與該格同列的上下各2個格子以及與該格同行的左右各1個格子以及該格子本身翻面。
例如,對於一個5*5的棋盤,僅對第三行第三列的格子進行該操作,得到如下棋盤(0表示白麵向上,1表示黑麵向上)。
00100
00100
01110
00100
00100
對一個棋盤進行適當的操作,使得初始棋盤(都是白麵朝上)變成已給出的目標棋盤的操作集合稱作一個解法。
小杉的任務是對給出的目標棋盤求出所有解法的總數。
Input
每組測試資料的第一行有3個正整數,分別是N和M和T(1<=N,M<=20,1<=T<=5)
接下來T個目標棋盤,每個目標棋盤N行,每行M個整數之前沒有空格且非0即1,表示目標棋盤(0表示白麵朝上,1表示黑麵朝上)
兩個目標棋盤之間有一個空行。
特別地,對於30%的資料,有1<=N,M<=15
Output
對每組資料輸出T行,每行一個整數,表示能使初始棋盤達到目標棋盤的解法總數
Sample Input
4 4 2
0010
0010
0111
0010
0010
0110
0111
0010
Sample Output
1
1
Hint
【樣例解釋】
對於輸入的資料,兩個目標棋盤各有一種解法
1:
0000
0000
0010
0000
2:
1011
1101
0111
1011
其中1表示對該格進行操作,0表示不操作
反思&題解
比賽思路: 懵……暴力都懶得打(聽說直接輸出1有50分!)
正解思路: 我們考慮列舉每一列的每個數,分析題目,發現如果一個點它的左邊時1,自己也是1時,就需要進行翻轉,我們可以列舉一個第0列的狀態,於是便有了一個\(O(mnT*2^n)\)的演算法,理論上可能過得了,但是常數有點大。
我們考慮優化,很顯然0101的東西很容易想到位運算,於是我們就可以將每一列的狀態記錄到一個二進位制數裡面,再用位運算轉移,時間複雜度便可以少一個n
CODE
#include<bits/stdc++.h> using namespace std; int n,m,t,a[25][25],zt[25],ztt[25],ans; int main() { scanf("%d%d%d",&n,&m,&t); while (t--) { char ch=getchar(); int i,j; for (i=1;i<=n;i++) { for (j=1;j<=m;j++) { ch=getchar(); if (ch=='0') a[i][j]=0; else a[i][j]=1; } ch=getchar(); } memset(zt,0,sizeof(zt)); for (i=1;i<=m;i++) { int num=1; for (j=n;j>=1;j--) { if (a[j][i]==1) zt[i]+=num; num*=2; } } ans=0; int s; for (s=0;s<=(1<<n)-1;s++) { zt[0]=s; for (i=1;i<=m;i++) ztt[i]=zt[i]; for (i=1;i<=m;i++) { zt[i]=(zt[i]^(zt[i-1]<<1)^(zt[i-1]<<2)^(zt[i-1]>>2)^(zt[i-1]>>1)^zt[i-1])&(1<<n)-1; zt[i+1]=(zt[i+1]^zt[i-1])&(1<<n)-1; } if (zt[m]==0) ans++; for (i=1;i<=m;i++) zt[i]=ztt[i]; } printf("%d\n",ans); } return 0; }