poj 2411 Mondriaan's Dream (狀態壓縮dp)
阿新 • • 發佈:2018-12-15
題目連結:哆啦A夢傳送門
題意:給出n*m的方塊,讓你用1*2的方塊去填滿它,問:有多少種不同的方案?
題解:
參考連結:https://blog.csdn.net/u014634338/article/details/50015825
看完題解,大概就這幾個核心點。
1,我們用二進位制來表示橫放和豎放,如圖所示:
2,我們已經知道橫放和豎放的規則,那麼我們只需列舉二進位制,判斷第i+1行是否跟第i行產生不相容。
首先我們先初始化第1行。
然後我們可以分類下判斷兼不相容的情況:
(i+1,j) (表示第i+1行,j列)
(i+1,j)為1時,(i,j)為0,滿足條件(豎放)
(i,j) 為1,必須(i+1,j+1)和(i, j+1) 必須為1才滿足條件(兩個橫放)
(i+1,j)為0時,(i,j)為1,滿足條件(橫放與豎放)
其它情況都為不相容。
程式碼:
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; typedef long long LL; LL dp[15][1<<12]; int n,m; ///設定初始狀態 bool init(int status) { for(int j=0;j<m;) ///前j-1列符合要求,對第j列進行判斷 { if(status&(1<<j)) ///第j列為1 { if(j==m-1) return false; ///j為最後一列 if(status&(1<<(j+1)))///第j列和第j+1都為1,則表示橫放,可行,考慮j+2列 j+=2; else ///第j列為1,j+1列為0,不可行 return false; } else j++; ///第j列為0,則為豎放,可行 } return true; } ///判斷上一次的狀態和本次狀態是否相容 bool check(int now,int pre) { for(int j=0;j<m;) { if(now&(1<<j)) ///第i行第j列為1 { if(pre&(1<<j)) ///第i-1行第j列也為1,那麼第i行必然是橫放 { ///第i行和第i-1行的第j+1列都必須是1,否則是非法的 if(j==m-1||!(now&(1<<(j+1)))||!(pre&(1<<(j+1)))) return false; else j+=2; } else j++; ///第i-1行第j列為0,說明第i行第j列是豎放 } else{ ///第i行第j列為0,那麼第i-1行的第j列應該是已經填充了的,必須為1 if(pre&(1<<j)) j++; else return false; } } return true; } void solve() { int tot=(1<<m)-1; memset(dp,0,sizeof(dp)); for(int i=0;i<=tot;i++){ ///初始化第1行 if(init(i)) dp[1][i]=1; } for(int i=2;i<=n;i++) { for(int j=0;j<=tot;j++) ///第i行的狀態 { for(int k=0;k<=tot;k++) ///第i-1行的狀態 { if(check(j,k)) dp[i][j]+=dp[i-1][k]; } } } printf("%lld\n",dp[n][tot]); } int main() { while(~scanf("%d%d",&n,&m)) { if(n==0&&m==0) break; if(n&1&&m&1) { ///都為奇數 printf("0\n");continue; } if(m>n) swap(n,m); ///交換下,使得m較小,減少狀態量 solve(); } return 0; }
總結下:這裡用二進位制來表示橫放與豎放,就用得很巧妙,我們要想要用狀態壓縮去做題,必須要找到適合題目要求的獨特二進位制表示,
這裡橫放用1 1 ,豎放用0|1 ,就是獨特的二進位制來表示,之後我們就去列舉,看是否衝突。
我的標籤:做個有情懷的程式設計師。