[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;
}