1. 程式人生 > 實用技巧 >紀中暑假集訓 2020.08.10【NOIP提高組】模擬 T3:玩詐欺的小杉

紀中暑假集訓 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;
}