[POJ 2411] Mondriaan's Dream

题面

思路比较巧妙的一道状压dp, (或许是因为我太菜了做的题太少没有看到过这种做法???)

状态表示为\(f[i][j]\)为第\(i\)行状态为\(j\), 我们假设某一个格子被一个竖着的块的上方所占据为1, 其余的状态为0, 我们设第\(i\) - \(1\)行状态为\(k\), 第\(i\)行状态为\(j\), 则\(k\)转移至\(j\)必须要满足以下几点:

1.\(j\)\(k\)进行与运算后结果为\(0\), 这个很好理解, 因为你两行是挨着的, 所以你上一行放了一个竖着的块的上半部分, 下半部分应该放在接下来一行的对因位置, 但你接下来一行的这个对应位置又放了一个竖着的块的上半部分, 显然矛盾, 也就是\(1\)下面必须是\(0\), 代表补全了这一个块

2.\(j\)\(k\)执行或运算的结果的二进制表示每一段\(0\)都必须是偶数个, 这个也很好理解, 或运算之后就将竖着的块的情况挑出来了, 剩下的位置肯定是要放横着的块的, 这要求我们必须将一段连续的\(0\)填满, 所以这段\(0\)必须是偶数

所以接下来我们就可以写代码了(愉快的\(coding\)时间)

注意附初值, \(f[0][0]\)\(1\), 其他为\(0\), 满足第二个条件的数可以预处理一下

具体代码

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;

int n, m;
long long f[2][(1 << 11) + 5];
bool check[15][(1 << 11) + 5]; 

inline int read()
{
	int x = 0, w = 1;
	char c = getchar();
	while(c < '0' || c > '9') { if (c == '-') w = -1; c = getchar(); }
	while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
	return x * w;
}

bool Check(int y, int x)
{
	int cnt = 0, num = 0;
	while(x)
	{
		num++; 
		if(x & 1)
			if(cnt % 2) return 0;
			else cnt = 0;
		else cnt++;
		x >>= 1; 
	}
	return !((y - num) % 2); 
}

int main()
{
	for(int i = 1; i <= 11; i++)
		for(int j = 0; j < (1 << 11); j++) check[i][j] = Check(i, j);
	while(scanf("%d%d", &n, &m) != EOF && n + m)
	{
		memset(f, 0, sizeof(f)); 
		f[0][0] = 1;
		for(int i = 1; i <= n; i++)
		{
			int opt = i & 1; 
			memset(f[opt], 0, sizeof(f[opt]));
			for(int j = 0; j < (1 << m); j++)
				for(int k = 0; k < (1 << m); k++)
					if(!(j & k) && check[m][(j | k)])
						f[opt][j] += f[opt ^ 1][k]; 
		}
		printf("%lld\n", f[n & 1][0]); 
	}
	return 0;
}
posted @ 2019-04-25 11:37  ztlztl  阅读(117)  评论(0编辑  收藏  举报