Mondriaan's+Dream POJ 2411 狀壓dp

題目鏈接:poj 2411

題目大意:對於一個n*m的矩陣,可以放1*2的牌,橫着放或者豎着放,問有多少种放的方法。

我們考慮每一行是把牌橫着放還是豎着放,顯然豎着放的時候會影響到下一行,所以我們把豎着放的位置用二進制的1表示,並且是表示這個位置是豎着放的牌的上半部分(可能有些繞 仔細理解一下) 

那麼對於一行的狀態我們用一個二進制的數如上面的說法表示,如何轉移呢,要滿足一下兩點:

  1. 上一行的狀態和本行的狀態&運算爲0 這個比較好理解 因爲上一行爲1的位置表示豎着放的牌的上半部分,在這一行肯定就是下半部分了,我們用二進制位的0表示,同理,另外這一行爲1的位置肯定上一行不能爲1。
  2. 上一行的狀態和本行的狀態進行|運算後得到的二進制數得滿足:0都是偶數個連續出現的  這點我們仔細想想也能明白,上一行和本行或運算以後就可以表示本行哪些位置的牌是豎着放了(這些位置是1) 那麼剩下的位置就是橫着放的,肯定得是偶數個0才滿足

  對於滿足條件2的這些數我們可以預處理一下 然後以行爲階段就可以進行dp了 

f[i][j]表示前 i 行,當前行狀態爲 j,可以得到的方法數

我們枚舉上一行的狀態 k 對於滿足條件的狀態 f[i][j]+=f[i-1][k]

最終我們需要的答案是 f[n][0] (最後一行顯然不需要一個豎着放的牌的上半部分)

#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
bool check[1<<11];
ll f[12][1<<11];
int n,m;
int main(){
	while(scanf("%d%d",&n,&m),n||m){
		memset(check,false,sizeof(check));
		for(int i = 0; i < (1<<m); i++){//預處理部分
			int flag = 1,cnt = 0;
			for(int j = 0; j < m; j++){
				if(i>>j&1){
					if(cnt%2){
						flag=0;
						break;
					}
					cnt=0;  
				}else cnt++;
			}
			if(cnt%2) flag=0;
			check[i]=flag;
		}
		//for(int i = 1; i < (1<<m); i++) printf("i=%d che[i]=%d\n",i,check[i]);
		f[0][0]=1;
		for(int i = 1; i <= n; i++){
			for(int j = 0; j < (1<<m); j++){
				f[i][j]=0;
				for(int k = 0; k < (1<<m); k++)
				if(((j&k)==0)&&check[j|k]){
					f[i][j]+=f[i-1][k];
				}
			}
		}
		printf("%lld\n",f[n][0]);
	}
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章