狀態壓縮DP
經典覆蓋問題,輸入n和m表示一個n*m的矩形,用1*2的方塊進行覆蓋,不能重疊,不能越出矩形邊界,問完全覆蓋完整個矩形有多少種不同的方案
其中n和m均為奇數的話,矩形面積就是奇數,可知是不可能完全覆蓋的。接着我們來看n*m為偶數的情況
DP前先處理一下,交換n和m使n較大m較小,這樣能減少狀態數
另外數據中是有重復的,所以開辟一個ans數組來記錄每組數據的結果,如果遇到相同的數據則不要計算直接輸出答案
不用這個ans數組的話也不會超時,這個代碼是跑出了950ms,加了這個記錄答案的數組時間變為600ms
接着就看注釋部分的講解即可
/* 最上面的為第1行,最下面為第n行 從上到下按行DP 其中一行的狀態我們用一個二進制表示,0表示沒有被覆蓋,1表示被覆蓋了 最后得到一個01串,這個串變回十進制就是一個狀態 定義狀態dp[i][s],表示前i-1行已經放滿,第i行的狀態為s的方案數 狀態轉移方程為 dp[i][s]=sum{ dp[i-1][ss] } ,其中狀態s與狀態ss兼容 這個狀態轉移方程的內涵在於理解s和ss何為兼容 首先我們約定一個放置方法,就是豎着放的時候,我們暫且將其稱為“上凸型擺放” 因為豎放必然占據第i-1行和第i行,我們約定這個方塊是屬於第i行的,也就是說它凸上去了 那么要在第i行的第j列豎放一個方塊的話,第i-1行第j列必須沒有方塊 也就是說,第i行的放置是受到第i-1行的限制的,反過來說在第i行豎放了方塊,也會影響第i-1行的狀態 所以這樣就可以講解一下狀態轉移方程了,前i-2行已經放滿了,第i-1行的狀態為ss(dp[i-1][ss]) 此時在第i行開始放一些方塊,放的方法不定,可能橫放可能豎放,但是按這個方案放完后 第i-1行剛好被填滿,且第i行的狀態變為了s,所以不難想到第i-1行的狀態ss到第i行的狀態s這個轉移是唯一的 所以有 dp[i][s]=sum{ dp[i-1][ss] } 最后我們詳細討論一下s和ss在什么情況下是兼容的 1.第i行的第j列為1,第i-1行的第j列為1,這樣的話,說明第i行的第j列一定不是豎放而是橫放否則會與第i-1行的第j列沖突 所以馬上緊接着判斷第i行第j+1列,如果是1,那么滿足橫放的規則,同時也要第i-1行第j+1列也要為1,否則的話這個格子沒辦法填充, 成立后向左移動兩格 不滿足上述條件的,就是兩個不兼容或者不合法的狀態 2.第i行第j列為1,第i-1行第j列為0,那么說明第i行第j列應該豎放並填充第i-1行第j列,成立后向左移動一格 3.第i行第j列為0,說明不放方塊,那么第i-1行第j列必須為1,否則沒法填充這個格子。若第i-1行第j列也為0,不兼容不合法 (至於第i行第j列這個格子空着干什么,其實就是留出來給第i+1行豎放的時候插進來的) 那么目標狀態是什么,就是dp[n][maxs],maxs表示全部是1的串,即第n-1行以上全部覆蓋滿,第n行的狀態為maxs,即沒有空着的格子,也全部覆蓋滿了 即整個矩形全部被覆蓋滿了的狀態 最后是第1行的初始化問題,因為約定了“上凸型擺放”,所以第1行是不能豎放方格的,只能橫放方格, 每橫放一個必定占據兩個格子,所以在判斷一個狀態(那個01串)的時候,連着的1的個數必定為偶數,如果出現了單獨的1,說明不合法 */ #include <cstdio> #include <cstring> #define N 15 #define MAX (1<<11)+10 long long dp[N][MAX]; long long ans[N][N]; int n,m; bool init(int s) { for(int k=0; k<m; ) { if(s & (1<<k)) { if(k==m-1) return false; if(s&(1<<(k+1))) k+=2; else return false; } else k++; } return true; } bool ok(int s, int ss) { for(int j=0; j<m; ) if(s & (1<<j)) //第i行第j列為1 { if( ss & (1<<j)) //第i-1行第j列也為1,那么第i行必然是橫放 { //第i行和第i-1行的第j+1都必須是1,否則是非法的 if( j==m-1 || !(s&1<<(j+1)) || !(ss&(1<<(j+1))) ) return false; else j+=2; } else j++; //第i-1行第j列為0,說明第i行第j列是豎放 } else //第i行第j列為0,那么第i-1行的第j列應該是已經填充了的 { if(ss&(1<<j)) j++;//已經填充 else return false; } return true; } void solve() { int maxs; if(n<m) { n=n^m; m=n^m; n=n^m; } //交換后n是行m是列,m較小,那么狀態數也可以相應減少 maxs=(1<<m)-1; memset(dp,0,sizeof(dp)); for(int s=0; s<=maxs; s++) //枚舉第一行所有可能的狀態 if(init(s)) { dp[1][s]=1; //方案數都是1 //printf("%d\n",s); } for(int c=2; c<=n; c++) //按行dp for(int s=0; s<=maxs; s++) //第i行的狀態 for(int ss=0; ss<=maxs; ss++) //第i-1行的狀態 if(ok(s,ss)) dp[c][s] += dp[c-1][ss]; printf("%lld\n",ans[n][m]=ans[m][n]=dp[n][maxs]); } int main() { memset(ans,-1,sizeof(ans)); while(scanf("%d%d",&n,&m)!=EOF) { if(!n && !m) break; if(!ans[n][m]) { printf("%lld\n",ans[n][m]); continue; } if(n&1 && m&1) { ans[n][m]=ans[m][n]=0; printf("0\n"); continue; } solve(); } return 0; }