鋪磚問題(狀態壓縮DP)
阿新 • • 發佈:2019-02-08
給定n*m的格子,每個格子被染成了黑色或者白色。現在要用1*2的磚塊覆蓋這些格子,要求塊與塊之間互相不重疊,且覆蓋了所有白色的格子,但不覆蓋任意一個黑色格子。求一共有多少種覆蓋方法,輸出方案數對M取餘後的結果。
限制條件:
1<=n<=15
1<=m<=15
2<=M<=10^9
思路:用圖論的語言來說就是一個完美匹配;首先考慮列舉所有的解這一方法(即暴力搜尋),為了不重複統計,我們每次從最左上方的空格處開始放置,對於哪些格子已經被覆蓋過,使用一個bool used[maxn][maxm]陣列即可;
時間複雜度:O(n*m*2^(n*m)),無法在規定時間內求解,同時,遞迴函式的引數共有n*m*2^(n*m)種可能,也無法使用記憶化搜尋求解。稍後介紹狀態壓縮...
程式碼:
//輸入 int n,m; bool color[maxn][maxm];//false:白,true:黑 //現在檢視的格子是(i,j),used表示哪些格子已經覆蓋過 int rec(int i,int j,bool used[maxn][maxm]) { if(j==m)//到下一行 return rec(i+1,0,used); if(i==n)//已經覆蓋了所有的空格 return 1; if(used[i][j]||color[i][j])//不需要在(i,j)上放置磚塊 return rec(i,j+1,used); //嘗試2种放法 int res=0; used[i][j]=true; //橫著放 if(j+1<m&&!used[i][j+1]&&!color[i][j+1]) { used[i][j+1]=true; res+=rec(i,j+1,used); used[i][j+1]=false; } //豎著放 if(i+1<n&&!used[i+1][j]&&!color[i+1][j]) { used[i+1][j]=true; res+=rec(i,j+1,used); used[i+1][j]=false; } used[i][j]=false; return res%M; } void solve() { bool used[maxn][maxm]; memset(used,0,sizeof(used));//初始化為false printf("%d\n",rec(0,0,used)); }
思路:仔細思考後會發現,實際上引數並沒有那麼多種可能;首先,由於黑色的格子不能被覆蓋,因此used裡對應的位置總是false,對於白色的格子,如果現在要在(i,j)位置上放置磚塊,那麼由於總是從最左上方的可放的格子開始放置,因此對於(i',j')<(i,j)(按字典序比較)的(i',j')總有used[i'][j']=true成立;此外,由於磚塊的大小為1*2,因此,不確定的只有每一列裡還沒查詢的格子中最上面的一個,共m個,從而可以把這m個格子通過狀態壓縮編碼進行記憶化搜尋;
時間複雜度:O(n*m*2^m)
程式碼:(運用集合的整數表示,詳情點選開啟連結)
int dp[2][1<<maxn];//DP陣列(滾動陣列迴圈利用) void solve() { int *crt=dp[0],*next=dp[1];//crt表示當前處理的格子,next類似一個臨時儲存的陣列 crt[0]=1; for(int i=n-1;i>=0;i--) for(int j=m-1;j>=0;j--){ for(int used=0;used<1<<m;used++) if((used>>j&1)||color[i][j]) //不需要在(i,j)放置磚塊 next[used]=crt[used&~(1<<j)]; else { //嘗試2种放法 int res=0; //橫著放 if(j+1<m&&!(used>>(j+1)&1)&&!color[i][j+1]) res+=crt[used|1<<(j+1)]; //豎著放 if(i+1<m&&!color[i+1][j]) res+=crt[used|1<<j]; next[used]=res%M; } swap(crt,next);//把next賦值給crt } printf("%d\n",crt[0]); }