題目鏈接:poj 2411
題目大意:對於一個n*m的矩陣,可以放1*2的牌,橫着放或者豎着放,問有多少种放的方法。
我們考慮每一行是把牌橫着放還是豎着放,顯然豎着放的時候會影響到下一行,所以我們把豎着放的位置用二進制的1表示,並且是表示這個位置是豎着放的牌的上半部分(可能有些繞 仔細理解一下)
那麼對於一行的狀態我們用一個二進制的數如上面的說法表示,如何轉移呢,要滿足一下兩點:
- 上一行的狀態和本行的狀態&運算爲0 這個比較好理解 因爲上一行爲1的位置表示豎着放的牌的上半部分,在這一行肯定就是下半部分了,我們用二進制位的0表示,同理,另外這一行爲1的位置肯定上一行不能爲1。
- 上一行的狀態和本行的狀態進行|運算後得到的二進制數得滿足:0都是偶數個連續出現的 這點我們仔細想想也能明白,上一行和本行或運算以後就可以表示本行哪些位置的牌是豎着放了(這些位置是1) 那麼剩下的位置就是橫着放的,肯定得是偶數個0才滿足
對於滿足條件2的這些數我們可以預處理一下 然後以行爲階段就可以進行dp了
表示前 i 行,當前行狀態爲 j,可以得到的方法數
我們枚舉上一行的狀態 k 對於滿足條件的狀態
最終我們需要的答案是 (最後一行顯然不需要一個豎着放的牌的上半部分)
#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;
}