1. 程式人生 > 實用技巧 >JZOJ2020年8月10日提高組T3 玩詐欺的小杉

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;
}