//题意:一个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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.