poj2411 Mondriaan’s Dream
题 意:有一个n*m的矩阵,用1 乘 2的小矩阵去填充,完全填充,问有多少种方案。
如图展示了2乘2的矩阵的填冲的方法
数据范围:
1<=w<=11
1<=h<=11
思 路:状压dp思路,数值一般不超过16。如果小矩阵横着放,则用两个连续的1,1来表示。如果竖着放则上面用0,下面用1来表示。这样小矩阵的填充方式就唯一对应一种填充的方式。下面来考虑递推关系。
那么怎么选出第一行可行的状态呢?
这里由一个小技巧,设第0行全是1。然后确定第一行和后面的可行状态就都可以用同
如果第i-1是1 那i行则可以是0或者1,如果是i那么连续的偶数个都得是1.如果i-1是0那么第i行则必须是1
dp[i][j] 第i行对应装填为j的最大方法数。
path[i][0/1] i表示总共由多少总对应方案,path[i][0]表示上一行的状态,path[i][1]表示上下一行对应的状态.
dp[0 ][ (1 << m)-1] =1
dp[i][path[case][1]] += dp[i-1][path[case][0]]
收 获:明白了加行,使得状态转移更好确定。,加深的对状压dp的了解
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 1000005
using namespace std;
typedef long long ll;
const int maxn = 1e6+5;
ll dp[12][(1<<12)+1]; //dp[i][j] 前i行 状态为j对应的方案数
int path[maxn][2];
int n,m,total;
void init(){
memset(dp,0,sizeof(dp));
memset(path,0,sizeof(path));
total = 0;
}
void get(){ //获取兼容模式
for(int i=0;i<(1<<m);i++){
for(int j=0;j<(1<<m);j++){
bool flag = true;
for(int k=0;k<m;k++)if(flag){
if((i&(1<<k)) != 0){ // i是1
if((j&(1<<k)) == 0){
continue;
} // j和i都是1
k++; //比较后面一位
if( (k<m) && (i&(1<<k)) != 0 && (j&(1<<k)) != 0){
continue;
}else{
flag = false;
break;
}
}else{
if((j&(1<<k)) != 0) continue; //必须要等于1
else{
flag = false;
break;
}
}
}
if(flag){
path[total][0] = i;
path[total++][1] = j;
}
}
}
}
int main() {
while(~scanf("%d %d",&n,&m) && (n+m)){
init();
get();
dp[0][(1<<m)-1] = 1;
for(int i=1;i<=n;i++){
for(int j=0;j<total;j++){
dp[i][path[j][1]] += dp[i-1][path[j][0]];
}
}
printf("%lld\n",dp[n][(1<<m)-1]);
}
return 0;
}