一開始根本不會狀壓dp,上網各種找題解,但發現他們寫的都很......反正我作為一個沒有接觸過狀態壓縮的,根本看不懂!
然后看了好多狀態壓縮的題的題解,總結了一下思路,思路很重要,有了思路轉換成計算機語言就好了。因此我先講一下思路:
先說說地圖,地圖上每一行的01代表一個狀態,比如輸入樣例中的111、010,表示第一行的三個位置都可以種稻子,第二行中間的位置可以種稻子,然后,不能種稻子的地方一定不能種稻子(廢話...)
可以種稻子的地方可以選擇種也可以選擇不種,然后有一個前提條件,就是上下左右相鄰的地方不能種稻子。
再說說怎么狀態壓縮,狀態壓縮就是把每一個狀態壓縮成二進制,二進制就是由01組成的,0代表不種,1代表種。二進制就要牽扯到位運算,位運算我就不想說了,百度吧。因此,一串01的二進制數就
可以代表一個狀態,例如輸入樣例第一行是111,那么可以放入第一行的狀態有,100、010、001、101、000,因為相鄰位置不能放所以只有5種方法,那么第二行就只有2種方法000、010(不考慮其他行)
那么看第一行和第二行(第一行——第二行),100——000,010——000,001——000,101——000,000——000,這是5種對應方法,還可以100——010,001——010,101——010,000——010這是另外的4種對應方法(第一行5種狀態對吧?第二行2種狀態,按照乘法原理,應該有5*2 = 10種方法,但是111——010是不合法的,因此樣例的答案是10-1 = 9)。
dp[i][j]意思是推到第i行狀態為j的方案總數。
那么“100——000”即為dp[2][000]可以由dp[1][100]得到,那么dp[2][000] = dp[2][000] + dp[1][100];
那么“010——000”即為dp[2][000]可以由dp[1][010]得到,那么dp[2][000] = dp[2][000] + dp[1][010];
......
以此類推,逐行遞推。
總結一下思路:先枚舉第一行,把所有可能的狀態和第一行的地圖對比,如果成功,則在循環里繼續枚舉第二行,把所有可能的狀態和第二行的地圖對比,如果成功,再和第一行填入的狀態對比,如果又匹配成功,則dp[2][000] = dp[2][000] + dp[1][100];方法數加到第二行。這就是一次循環結束了,從新枚舉第二行...
把思路轉換成代碼
can[]代表可行的狀態,稍后解釋。cur[i]代表地圖的第i行
1 for(int i=1;i<m;i++)//枚舉每一行 2 { 3 for(int j=0;j<tot;j++)//對第i行枚舉所有可行的狀態j 4 { 5 if((can[j]&cur[i])==0)//如果狀態j和第i行匹配了 6 { 7 for(int k=0;k<tot;k++)//枚舉第i+1行的所有可行的狀態k 8 { 9 if(((can[k]&cur[i+1])==0)&&((can[k]&can[j])==0))//狀態k和第i+1行匹配且和狀態j匹配 10 dp[i+1][can[k]] = dp[i+1][can[k]]+dp[i][can[j]];//狀態數相加 11 } 12 } 13 } 14 }
這樣核心代碼就實現了。
有一個小方法,就是枚舉可行狀態的時候,假如一行是8列,不必從00000000枚舉到11111111,這樣很麻煩,所以要預處理。
就是在一開始把,一行的可行狀態先求出來就拿“11111111”來說,這肯定是不可能的,因為有相鄰的1,所以在一開始就可以舍棄掉。怎么做呢?
假如一行是8列,先從00000000枚舉到11111111,對於每一個狀態把它左移1位,再和他自己&運算,假如結果>0,就說明有有相鄰的1,舉個簡單的例子:
01011要判斷有沒有相鄰的1,if(((01011<<1) & (01011)) > 0 )則有相鄰的1,(01011<<1) & (01011) 就是 010110和01011按位且運算,這兩個紅色地方1&1 == 1,因此結果大於0。
怎么實現呢?
1 tot = 0;//全局變量,相當於棧的top,代表可行的狀態數
2 for(int i=0;i<(1<<n);i++)//n是列數,i是枚舉的狀態
3 if((i&(i<<1))==0) can[tot++] = i;
dp[][]肯定要初始化對吧?不然全是0了,只要對第一行初始化就行了,因為后面的的行都是由第一行得來的
1 for(int i=0;i<tot;i++) 2 if((cur[1]&can[i])==0) dp[1][can[i]] = 1;//和cur[1](第一行)匹配,就給對應的dp賦值為1
最后一步就是得到cur[]
1 for(int i=1;i<=m;i++) 2 { 3 for(int j=0;j<n;j++) 4 { 5 int num; 6 scanf("%d",&num); 7 if(num==0) cur[i] = (cur[i]|(1<<j));//這里要給0的地方變為1,1的地方放上0,因為要保證不合法的匹配一定是獨一無二的。自己思考一下吧
8 } 9 }
最后貼一下完整代碼,一開始學的時候,感覺主流代碼都一模一樣,而且一大堆亂七八糟的函數,麻煩又看不懂,於是下定決心如果自己搞明白了,一定要寫一個大家都看得懂的題解,感覺自己講的比其他都清楚了,如果看不懂就真沒辦法了......

1 #include<cstdio>
2 #include<cstring>
3 #include<algorithm>
4 #define mod 100000000
5
6 using namespace std; 7 int dp[13][1<<12],cur[13]; 8 int can[1<<12],tot,m,n; 9
10 int main() 11 { 12 while(~scanf("%d%d",&m,&n)) 13 { 14 tot = 0; 15 for(int i=0;i<(1<<n);i++) 16 if((i&(i<<1))==0) can[tot++] = i; 17 memset(cur,0,sizeof(cur)); 18 memset(dp,0,sizeof(dp)); 19 for(int i=1;i<=m;i++) 20 { 21 for(int j=0;j<n;j++) 22 { 23 int num; 24 scanf("%d",&num); 25 if(num==0) cur[i] = (cur[i]|(1<<j)); 26 } 27 } 28 for(int i=0;i<tot;i++) 29 if((cur[1]&can[i])==0) dp[1][can[i]] = 1; 30 for(int i=1;i<m;i++) 31 { 32 for(int j=0;j<tot;j++) 33 { 34 if((can[j]&cur[i])==0) 35 { 36 for(int k=0;k<tot;k++) 37 { 38 if(((can[k]&cur[i+1])==0)&&((can[k]&can[j])==0)) 39 dp[i+1][can[k]] = dp[i+1][can[k]]+dp[i][can[j]]; 40 } 41 } 42 } 43 } 44 int ans = 0; 45 for(int i=0;i<tot;i++) 46 { 47 ans += dp[m][can[i]]; 48 ans = ans % mod; 49 } 50 printf("%d\n",ans); 51 } 52 }