1. 程式人生 > >鋪磚問題(狀態壓縮DP)

鋪磚問題(狀態壓縮DP)

給定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]);
 }