1. 程式人生 > 實用技巧 >玩詐欺的小杉——異或優化的狀壓dp

玩詐欺的小杉——異或優化的狀壓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(2n

NMT)會超時,此時我們應試著研究轉移過程。我們會發現,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; }