T解 POJ-2411 Mondriaan's Dream [轮廓线DP] || [状压DP]

来源:互联网 发布:天津基础教育网络平台 编辑:程序博客网 时间:2024/04/30 12:10

大家都很强,可与之共勉。

今天考试遇到了一道神题,用俄罗斯方块铺地板地板上还有柱子(数据范围十分感人)。std是要用轮廓线DP。在此之前没有听说过轮廓线DP,故开始学习。当然看到这一道入门级的题。最初做的时候是用的状压DP(其实是正解+打表)。

轮廓线DP显然是按点DP,分析该点之前的状态,若合法,则从该状态更新。初始化即为f[0][(1 << m) - 1] = 1(希望大家自己想清楚为什么这么做,我也想了好一会儿)。
是这样的初始值为f[0][<11111>]=1,其他f[0][其他值]=0(其实这个可以不弄,因为首行不能上放,所以首行轮廓线状态数不可能会引用到f[0][01111]这样的初值。)。

首先分析状态,我们发现这一一个点的状态只与上一个点有关,所以用滚动数组强行优化空间(否则DP数组就需要n * m * ( 1 << m - 1 )的空间)。

默认枚举点的顺序是从上到下,从左到右, 二进制bit(1)代表放了,bit(0)表示没有放。
如此分析,有几种情况呢?
答案是三种:1.不放 2.横着放(占着当前点与左边的点, 为什么是左边的呢, 因为状态要从前面的点转移啊!)3.竖着放(占着这个与正上方那一个)

设k为第(i, j)点的待判定状态。
接下来就是判合法,我们可以确定,对于1如果这个点的正上方没放,那么就不合法。如何判断呢?

if(k & (1 << (m - 1)))  f[cur][(k << 1)^ (1 << m)] += f[cur ^ 1][k];//等价于if((k << 1)& (1 << m))  f[cur][(k << 1)^ (1 << m)] += f[cur ^ 1][k];

不难理解,画一个图模拟就好。

对于2,还有一个限制条件,就是这一个点不能处于第一列(用0表示),此外要求它的左边没有,正上方就一定要有(这也是每一个状态所要求的)。最后把最正上方的点改为1,把正上方左边的点标为0(^(1 << m))(因为只能转移m位的状态,留着也用不上)。

    if(j && !(k & 1))  if(((k << 1) ^ 3) & (1 << m))  f[cur][((k << 1) ^ 3) ^ (1 << m)] += f[cur ^ 1][k];

对于3,要求正上方没有,同理判断,这个点不能处于第一行(同样用0表示)。

if(i && !(k & (1 << (m - 1))))  if( ((k << 1) ^ (1 << m) ^ 1) & (1 << m) ) f[cur][((k << 1) ^ (1 << m) ^ 1) ^ (1 << m)] += f[cur ^ 1][k];

那么显而易见,完整代码如下(判断和非法有点繁琐,可以优化)

#include "cstdio"#include "cstring"#define swap(a, b)  {a ^= b, b ^= a, a ^= b;}typedef long long LL;int n, m, cur, pre;LL f[2][1 << 11], a[50][101];int main()  <%    memset(a, 0, sizeof(a));    while(scanf("%d%d", &n, &m) == 2 && n && m)  {        if(m > n)   swap(m, n);        if(n & 1 && m & 1)  {            puts("0");            continue;        }        if(a[m][n])  {            printf("%lld\n", a[m][n]);            continue;        }        memset(f, 0, sizeof(f));        cur = 0;        f[0][(1 << m) - 1] = 1;         for (int i = 0; i < n; ++i)            for(int j = 0; j < m; ++j)  {                cur ^= 1;                memset(f[cur], 0, sizeof(f[cur]));                for(int k = 0; k < (1 << m); ++k)  {                    if((k << 1) & (1 << m))  f[cur][(k << 1) ^ (1 << m)] += f[cur ^ 1][k];                    if(i && !(k & (1 << (m - 1))))  if( ((k << 1) ^ (1 << m) ^ 1) & (1 << m) ) f[cur][((k << 1) ^ (1 << m) ^ 1) ^ (1 << m)] += f[cur ^ 1][k];                    if(j && !(k & 1))  if(((k << 1) ^ 3) & (1 << m))  f[cur][((k << 1) ^ 3) ^ (1 << m)] += f[cur ^ 1][k];                }            }        printf("%lld\n", (a[m][n] = f[cur][(1 << m) - 1]));    }%>

当然给大家我当初Naive的状压DP版本(单组数据的)

#include <cstdio>#include <iostream>#include <algorithm>using namespace std;long long f[12][(1 << 11) +1];int r,c;inline bool init(int i)  {// 1 * 2;    int k = 0;    while( k < c )  {         if(i & (1<<k))  {            if(k == c - 1) return false;            if( i & (1 << (k+1)) ) k += 2;            else return false;        }  else ++k;    }    return true;}inline int judge( int now, int pre )  {        int k = 0;        while(k < c)  {            if(now & ( 1 << k ) )                if( pre & ( 1 << k ) )                    if( k == c - 1 || !( now & ( 1 << ( k + 1 ) ) ) || !( pre & ( 1 << ( k + 1 ) ) ) ) return false;                    else  k += 2;                else ++k;            else                if( pre & ( 1 << k ) ) ++k;                else return 0;       }       return 1;}long long dp( int r, int c )  {        for ( int i = 0; i <= (1 << c) - 1; ++i )            if(init(i))    f[1][i] = 1;        for ( int l = 2; l <= r; ++l )            for ( int now = 0; now <= (1 << c) - 1; ++now )                for ( int pre = 0; pre <= (1 << c) - 1; ++pre )                    f[l][now] += f[l-1][pre] * judge(now, pre);        return f[r][(1<<c) - 1];}int main()  {        freopen("domino.in","r",stdin);freopen("domino.out","w",stdout);        scanf("%lld%lld", &r, &c);         if (r < c)  swap( r, c );//lower the number of situations;        printf("%lld\n", (r & 1 && c & 1) ? 0 : dp( r, c ) );        return 0;}

借我爸的一句话

非上上智, 不了了心。

0 0