JZOJ2020年8月10日提高組T3 玩詐欺的小杉
JZOJ2020年8月10日提高組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表示不操作
題解
題意
給出一個矩陣,問經過多少次操作使得原矩陣(全0)轉換成目標矩陣
每次操作將會翻轉當前格子和左右各一個及上下各兩個的顏色
分析
題目轉一下:多少操作使得目標變成全0
很容易想到狀壓\(DP\)(今天第3道)
發現,上一列的情況會影響當前這列的選擇
看:
00000
00000
01000
00000
00000
現在我們做到了第3列
那麼第3列第3行這個位置就必須要翻轉
因為到了第4列就無法影響到第2列的那個1了
所以說我們可以構造第0列的01情況
然後根據每列的01情況選取位置進行翻轉
最後看一下第\(m\)列是否為全0即可
時間複雜度\(O(2^n*n^3)\)
思考優化
發現翻轉和異或十分相似
看一下樣例1
00100 00100 01110 00100 00100
第2行和第3行異或
00100
00100
01010
00100
00100
把第2行的狀態往下一位,再異或第3行
00100
00100
01010
00000
00100
同理就可以搞定第3列
那麼第4列再和第2列異或
00000
00000
01000
00000
00000
搞定
優化至\(O(2^n*n^2)\)
#include<cstdio>
#include<cstring>
using namespace std;
int n,m,t,mx,i,j,ans,er,a[25][25],map[25],c[25];
char ch;
int main()
{
scanf("%d%d%d",&n,&m,&t);
mx=(1<<n)-1;
while (t--)
{
ans=0;
memset(map,0,sizeof(map));
for (i=1;i<=n;i++)
{
j=1;
ch=getchar();
while (ch!='0'&&ch!='1') ch=getchar();
while (ch=='0'||ch=='1')
{
a[i][j]=ch-'0';
ch=getchar();
j++;
}
}
for (j=1;j<=m;j++)
{
er=1;
for (i=1;i<=n;i++)
{
map[j]+=a[i][j]*er;
er*=2;
}
}
for (map[0]=0;map[0]<=mx;map[0]++)
{
for (i=0;i<=m;i++)
c[i]=map[i];
for (i=1;i<=m;i++)
{
c[i]=(c[i]^c[i-1]^(c[i-1]*2)^(c[i-1]*4)^(c[i-1]/2)^(c[i-1]/4))&mx;
c[i+1]=(c[i+1]^c[i-1])&mx;
}
if ((c[m]&mx)==0) ans++;
}
printf("%d\n",ans);
}
return 0;
}