您的位置:首页 > 其它

【POJ 2411】Mondriaan's Dream(状压dp)

2016-03-02 17:28 567 查看
【POJ 2411】Mondriaan's Dream(状压dp)

Time Limit: 3000MSMemory Limit: 65536K
Total Submissions: 14107Accepted: 8152
Description
Squares and rectangles fascinated the famous Dutch painter Piet Mondriaan. One night, after producing the drawings in his 'toilet series' (where he had to use his toilet paper to draw on, for all of his paper was filled with squares
and rectangles), he dreamt of filling a large rectangle with small rectangles of width 2 and height 1 in varying ways.



Expert as he was in this material, he saw at a glance that he'll need a computer to calculate the number of ways to fill the large rectangle whose dimensions were integer values, as well. Help him, so that his dream won't turn into a nightmare!
Input
The input contains several test cases. Each test case is made up of two integer numbers: the height h and the width w of the large rectangle. Input is terminated by h=w=0. Otherwise, 1<=h,w<=11.

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
Ulm Local 2000

一个比较明显的状压。给出容器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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: