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

猜你喜欢

转载自www.cnblogs.com/ztlztl/p/10767422.html