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

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

Time Limit: 3000MS   Memory Limit: 65536K
Total Submissions: 14107   Accepted: 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

【POJ 2411】Mondriaan's Dream(状压dp)_第1张图片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;
}





你可能感兴趣的:(【POJ 2411】Mondriaan's Dream(状压dp))