POJ2411 轮廓线动态规划典型例题

Poj2411 Mondriaan’s Dream

给出一个n*m的矩形,然后用1*2大小的多米若骨牌去填充n*m的这个矩形,问有多少种填充方法。

分析:典型的轮廓线动态规划题目。详见刘汝佳新书:算法竞赛入门经典:训练指南P384.

首先本题目是以一个一个的格子为基础来计算状态的,即每次都是考虑当前位置的格子如何放左上骨牌(以当前位置为最右下角,即只不放,左放,和上放3种情况,没有右放和下放)。且本题的状态都是一条一条的轮廓线。如图:

1

1

1

1

1

1

1

K4

K3

K2

K1

K0

O

 

 

 

 

 

 

 

令<K3K2K1K0O> 为这5个数字对应的二进制值的十进制结果。K3K2K1K0O=11110表示由这5个格构成的轮廓线状态 如

K4 K3 K2 K1 K0 O 分别为: 0 1 1 1 1 0 时,

<K3K2K1K0O> = 30,<K4K3K2K1K0> = 15

本题的状态为d[cur][<K3K2K1K0O>]表示 当前以O格为末尾的轮廓线状态为<K3K2K1K0O>的值对应二进制表示时且K3之前的所有格子的值都为1时构成上图所给状态的方法总数。其中 当前轮廓线为K3K2K1K0O,前一个轮廓线为K4K3K2K1K0。当前的轮廓线状态数用d[cur][ <K3K2K1K0O>]表示,前一个轮廓线状态数用d[1-cur][ <K4K3K2K1K0>]表示。

现在初始时 K4K3K2K1K0O的值对应为 011110.

O格的每一种操作(不放,左方,上方)都会联系前后两种不同的轮廓线。

假设在O格放上骨牌,K4和O值都变为1,则d[cur][<K3K2K1K0O>] =d[cur][<11111>]  +=d[1-cur][<K4K3K2K1K0>]=d[1-cur][<01111>] 这句话的意义是 在O格放上骨牌,则以O格为末尾的轮廓线为11111,且已放的格子情况为:

1

1

1

1

1

1

1

1

1

1

1

1

1

 

 

 

 

 

 

 

 

 

 

 

 

的时候有d[1-cur][<01111>](这是一个整数值)这么多种情况是通过在轮廓线<K4K3K2K1K0>=<01111>的基础上,放O格上骨牌而产生来的。

假设在O格不放,K4的值不改变依然为0,如果执行:d[cur][<K3K2K1K0O>] =d[cur][<11110>]  +=                            d[1-cur][<K4K3K2K1K0>]=d[1-cur][<01111>] 这样是非法的。因为不放O格,K4的值永远为0(K4的值如果为0只能通过O格放上骨牌来置1,O格之后的格子无论怎么放骨牌都不会改变K4的值),所以如果执行上述非法操作,违反了关于d[cur][<K3K2K1K0O>]的定义(要求K3之前所有格子的值都为1).

 

假设在O格放左骨牌,也是非法操作,因为O格左边已经是1了,不可能放的下左骨牌。

且在首行不能上放,在首列不能左放。

 

最后当循环处理到第(n,m)格时,轮廓线为<11111>时表示轮廓线前面的n-1行也全是11111,所以最终结果为d[cur][<11111>]。

并且初始值为d[0][<11111>]=1,其他d[0][其他值]=0(其实这个可以不弄,因为首行不能上放,所以首行轮廓线状态数不可能会引用到d[0][01111]这样的初值。)。

 

AC代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 15;
long long d[2][1<<maxn];
int n,m,cur;
void update(int a,int b)//a是包含m位1进制数的老状态,b是包含m+1位1进制数的新状态
{
    if(b&(1<<m)) d[cur][b^(1<<m)] += d[1-cur][a];//只有新轮廓线首位为1时才更新
}
int main()
{
    while( scanf("%d%d",&n,&m)==2&&n&&m )
    {
        if(m>n)swap(n,m);
        memset(d,0,sizeof(d));
        cur=0;
        d[cur][(1<<m)-1]=1;
        for(int i=0;i<n;i++)
            for(int j=0;j<m;j++)
            {
                cur ^=1;
                memset(d[cur],0,sizeof(d[cur]));
                for(int k=0;k<(1<<m);k++)//k的二进制形式表示前一个格子的轮廓线状态
                {
                    update(k,k<<1);//当前格不放,直接k左移一位就表示带m+1位的新轮廓线的状态
                    if(i && !(k&(1<<(m-1)) )) update(k,(k<<1)^(1<<m)^1);//上放,要求轮廓线首为0
                    if(j && (!(k&1)) ) update(k,(k<<1)^3);//左放,要求轮廓线尾0,首1
                }
            }

        printf("%I64d\n",d[cur][(1<<m)-1]);
    }
    return 0;
}

 

    原文作者:动态规划
    原文地址: https://blog.csdn.net/u013480600/article/details/19499899
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞