//題意:一個n*m的矩形用 1* 2 的卡牌填滿 問共有多少種方法
//方法:狀態壓縮+動規的思想 dp[i][j] 表示前i -1行全部填滿 第i行狀態爲j的方法總和
// 狀態J的二進制狀態 0 表示該位被佔用了 1 代表 該爲是空的
//狀態轉移方程:dp[i][j] = sum(dp[i - 1][k]) (狀態j可由狀態k推出)
//起初有一個地方我有點模棱兩可
//比如現在第i -1的狀態 爲 100111 它可以推導出 第i行的狀態爲 111111
//但是並不符合要求
//後來仔細想了想 111111 是可以由 100111推出來 但是實際上是由 (100111)的上一個狀態推出來的
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <algorithm>
#include <iostream>
using namespace std;
long long dp[13][1<<11];
long long ans[15][15];
int n, m;
int isok(int s, int ss);
void init()
{
int maxn = (1<<m) - 1;
memset(dp, 0, sizeof dp);
for(int i = 0; i <= maxn; i++) //第一行的各個狀態是由dp[0][maxn]推出來的
if(isok(i, maxn))
dp[1][i] += 1;
}
int isok(int s, int ss)
{
for(int k = 1; k <= m; )
{
if(s & (1<<(k - 1))) //如果第i行第k列爲1
{
if(ss & (1<<(k -1))) //如果第i-1行第k列爲1
{
//必須選擇橫放
if( (!(s & (1<<k))) || (!(ss &(1<<k))) || (k == m)) return 0; //如果 第i行第k+1列不爲1|| 第i-1行第k+1列不爲1 ||k已經在最後一位上 判定狀態不合法
k += 2; // 該位合法 查看兩位後
}
else k++; //該位合法 查看下一位
}
else{ //如果第i行第k列爲0
if( ! (ss & (1<<(k - 1)))) return 0; //如果第i - 1行第k列爲0 那麼狀態不合法
k++; // 該位合法 查看下一位
}
}
return 1;
}
int main()
{
while(cin>>n>>m && (n != 0))
{
if( (n&1) && (m&1)) {
cout<<0<<endl;
continue;
}
if(ans[n][m]) {
cout<<ans[n][m]<<endl;
continue;
}
if(n < m) swap(n, m); //使m小從而使得狀態儘可能少
init();
for(int i = 2; i <= n; i++) //模擬到第i行
for(int j = 0; j < (1<<m); j++) //模擬第i行狀態j
for(int k = 0; k < (1<<m); k++) //模擬第i-1行狀態k
if(isok(j, k)) //如果狀態k -> j合法
dp[i][j] += dp[i - 1][k];
ans[n][m] = ans[m][n] = dp[n][(1<<m) - 1];
cout<<ans[n][m]<<endl;
}
return 0;
}
poj 2411 (狀態壓縮dp)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.