标签:
【POJ 2411】Mondriaan‘s Dream(状压dp)
| Time Limit: 3000MS | Memory Limit: 65536K | |
| Total Submissions: 14107 | Accepted: 8152 |
Description

Input
Output
For each test case, output the number of different ways the given rectangle can be filled with small rectangles of size 2 times 1. Assume the given large rectangle
is oriented, i.e. count symmetrical tilings multiple times. Sample Input
1 2 1 3 1 4 2 2 2 3 2 4 2 11 4 11 0 0
Sample Output
1 0 1 2 3 5 144 51205
Source
一个比较明显的状压。给出容器size w*h 又固定了砖块大小1*2 砖块只有两种状态 一种横放一种竖放
设每个1x1的格子放为1未放为0 这样第i行放置状态只与i-1行有关 如果当前未知i-1行为0 那么i行一定要放 既该砖块竖放在i-1和i两行间
这样通过状压后从上往下一行行推 每次枚举状态 模拟判断是否合法即可 最后答案就是dp[w][h](因为最后一行必须铺满
代码如下:
#include <iostream>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <list>
#include <algorithm>
#include <map>
#include <set>
#define LL long long
#define Pr pair<int,int>
#define fread() freopen("in.in","r",stdin)
#define fwrite() freopen("out.out","w",stdout)
using namespace std;
const int INF = 0x3f3f3f3f;
const int msz = 10000;
const int mod = 1e9+7;
const double eps = 1e-8;
LL dp[2][1<<11];
int h,w;
bool cal(int pre,int now)
{
int cnt = 0;
for(int i = 0; i < h; ++i)
{
if(!(pre&1))
{
if(!(now&1)) return false;
if(cnt&1) return false;
cnt = 0;
}
else
{
if(!(now&1))
{
if(cnt&1) return false;
cnt = 0;
}else cnt++;
}
pre >>= 1;
now >>= 1;
}
return !(cnt%2);
}
bool can(int sit)
{
int cnt = 0;
while(sit)
{
if(sit&1) cnt++;
else
{
if(cnt&1) return false;
cnt = 0;
}
sit >>= 1;
}
if(cnt&1) return false;
return true;
}
int main()
{
//fread();
//fwrite();
while(~scanf("%d%d",&h,&w) && (h+w))
{
if(h > w) swap(h,w);
int tot = 1<<h;
for(int i = 0; i < tot; ++i)
dp[0][i] = can(i);
int pos = 1;
for(int i = 1; i < w; ++i, pos ^= 1)
{
memset(dp[pos],0,sizeof(dp[pos]));
for(int j = 0; j < tot; ++j)
for(int k = 0; k < tot; ++k)
if(cal(j,k,h)) dp[pos][k] += dp[pos^1][j];
}
printf("%lld\n",dp[pos^1][tot-1]);
}
return 0;
}
然而会发现这种特别暴力的方法灰常灰常慢,多亏出题人良心,比较卡边过。。2000+ms
之后看讨论版许多用位运算做的,由于第i行只与第i-1行相关,i-1行为0的地方i行必须为1 i-1行为1的地方i行不限制
这样枚举第i-1行状态,对于每个状态j ~j&(1<<h)就是i行必放的状态(~j将j取反 1变0 0变1 之后与1<<h且运算 抛去超出状态的位
之后再通过搜索 在可进行选择的位置进行枚举判断 看能不能放横砖(不可放竖砖 因为对于第i行 放竖砖是对于i-1行而言 就是是竖放在i-1与i行之间 跟i+1行无关,在枚举i行状态时 进行刚才的取反和且运算才会出现竖放在i和i+1行间的砖
这样会减去对很多没有必要的状态的枚举 神剪枝!!!Orz
代码如下:
#include <iostream>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <list>
#include <algorithm>
#include <map>
#include <set>
#define LL long long
#define Pr pair<int,int>
#define fread() freopen("in.in","r",stdin)
#define fwrite() freopen("out.out","w",stdout)
using namespace std;
const int INF = 0x3f3f3f3f;
const int msz = 10000;
const int mod = 1e9+7;
const double eps = 1e-8;
LL dp[2][1<<11];
LL add;
int h,w;
void cal(int id,int pos,int now)
{
if(pos == h)
{
dp[id][now] += add;
return;
}
cal(id,pos+1,now);
if(pos <= h-2 && ((now^(1<<pos))&(1<<pos)) && ((now^(1<<(pos+1)))&(1<<(pos+1)))) cal(id,pos+2,now|(1<<pos)|(1<<(pos+1)));
}
int main()
{
//fread();
//fwrite();
while(~scanf("%d%d",&h,&w) && (h+w))
{
memset(dp[0],0,sizeof(dp));
add = 1;
cal(0,0,0);
int pos = 1;
int tot = 1<<h;
for(int i = 1; i < w; ++i, pos ^= 1)
{
memset(dp[pos],0,sizeof(dp[pos]));
for(int j = 0; j < tot; ++j)
if(dp[pos^1][j])
{
add = dp[pos^1][j];
cal(pos,0,~j&(tot-1));
}
}
printf("%lld\n",dp[pos^1][tot-1]);
}
return 0;
}
【POJ 2411】Mondriaan's Dream(状压dp)
标签:
原文地址:http://blog.csdn.net/challengerrumble/article/details/50781488