棋盤問題(狀態壓縮例題1)
阿新 • • 發佈:2018-12-19
題目 有一個NM(N<=5,M<=1000)的棋盤,現在有12及2*1的小木塊無數個,要蓋滿整個棋盤,有多少種方式?答案只需要mod1,000,000,007即可。
分析 這道題在很久之前我就看到過,之前自己沒有打出來,然後身邊的隊友用的搜尋過的。然後最近初學狀壓DP,再次接觸到,看了很多例題都沒怎麼理解狀壓DP,直到看到這道題,感覺稍微懂了一點。以下放出分析:
在這道題目中,N和M的範圍本應該是一樣的,但實際上,N和M的範圍卻差別甚遠,對於這種題目,首先應該想到的就是,正確演算法與這兩個範圍有關!N的範圍特別小,因此可以考慮使用狀態壓縮動態規劃的思想:
假設第一列已經填滿,則第二列的擺設方式,只與第一列對第二列的影響有關。同理,第三列的擺設方式也只與第二列對它的影響有關。那麼,使用一個長度為N的二進位制數state來表示這個影響,例如:4(00100)就表示了圖上第二列的狀態。
因此,本題的狀態可以這樣表示:
dp[i][state]表示該填充第i列,第i-1列對它的影響是state的時候的方法數。i<=M,0<=state<2N
對於每一列,情況數也有很多,但由於N很小,所以可以採取搜尋的辦法去處理。對於每一列,搜尋所有可能的放木塊的情況,並記錄它對下一列的影響,之後更新狀態。狀態轉移方程如下:
dp[i][state]=∑dp[i-1][pre]每一個pre可以通過填放成為state
程式碼
#include<stdio.h> #include<stdlib.h> #include<math.h> #include<string.h> #include<algorithm> #include<iostream> #define exp 1e-9 #define PI acos(-1.0) #define INF 0x3f3f3f3f #define mod 1000000007 using namespace std; typedef long long LL; int n,m; LL dp[1005][35]; //1000列32個狀態 //第i列,列舉到了第j行,當前狀態是state,對下一列的影響是nex void dfs(int i,int j,int state,int nex) { if(j==n) { dp[i+1][nex]+=dp[i][state]; dp[i+1][nex]%=mod; return; } //如果這個位置已經被上一列所佔用,直接跳過 if( ( (1<<j)&state) > 0) dfs(i,j+1,state,nex); //如果這個位置是空的,嘗試放一個1*2的 if( ( (1<<j)&state) == 0) dfs(i,j+1,state,nex|(1<<j)); //如果這個位置以及下一個位置都是空的,嘗試放一個2*1的 if (j+1<n && ((1<<j)&state)==0 && ((1<<(j+1))&state)==0) dfs(i,j+2,state,nex); return; } int main() { while(~scanf("%d%d",&n,&m)) { memset(dp,0,sizeof(dp)); if(n==0 && m==0) break; dp[1][0]=1; for(int i=1;i<=m;i++) { for(int j=0;j<(1<<n);j++) { if(dp[i][j]) dfs(i,0,j,0); } } printf("%lld\n",dp[m+1][0]); } return 0; }