POJ 2411: Mondriaan's Dream

题目链接:http://poj.org/problem?id=2411


题意:

用1*2的骨牌填充一个n*m的网格。

求方案数。


算法:

这道题用普通的状态压缩DP的话,打表也可以过。

也就是用一个m位二进制数,0表示上一行的这个位置未被覆盖,1表示被覆盖。

然后逐行转移。

不过我用朴素的状态压缩优化到死的时候,也就知道这题一定有别的解法。


先来介绍一种用二进制枚举子集的方法,是zkc同学之前教给我的。

假设有一个集合S,它的元素是0~(k-1)中的一部分。

用一个k位二进制数mask代表这个集合。

mask的第x位为1表示x在这个集合中,

否则x不在这个集合中。‘

那么下面这个语句可以不重不漏的枚举出S的所有子集

for(int mask1=mask; mask1>=0; mask1=(mask1-1)&mask)


原理也比较简单。

当某一次-1操作使得原本不该出现1的地方出现了1。

也就是说,出现了一个不合法的后缀(这个后缀肯定是一连串的1)

那么&操作就相当于是在合法的前缀不变的情况下,这些不合法的后缀“跨过去”了。

直观地看,就是把这些1“减去”了。

在这个过程中,一定没有漏掉任何合法的情形,

因为这些1不减去的话肯定是不合法的。


这道题与以上的情形正好相反。

当我们知道上一行的覆盖情况,那么上一行未被覆盖的格子下一行必定要覆盖,

也就是有些格子是必取的。

那么可以用下面这个语句枚举出下一行的所有情形。

for(int mask1=mask; mask1<(1<<m); mask1=(mask 1+1)|mask)


当然,要求除了与上一行相接的格子(竖直放置骨牌)外,

其它被覆盖的格子要是两两一组的。

这个判断一下就可以了。


代码如下:

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<cmath>
#include<algorithm>
#include<queue>
#include<vector>
#include<stack>
#include<set>
#include<map>
#define INF 0x3f3f3f3f
using namespace std;

const int MAXN=11;
long long d[MAXN+1][MAXN+1][1<<MAXN];
int m;

bool judge(int mask)
{
    while(mask)
    {
        if(mask&1)
        {
            if((mask&3)!=3)
            {
                return false;
            }
            mask>>=2;
        }
        else
        {
            mask>>=1;
        }
    }
    return true;
}

int main()
{
    memset(d,0,sizeof(d));
    for(m=1; m<=11; m++)
    {
        d[0][m][(1<<m)-1]=1LL;
        for(int i=0; i<11; i++)
        {
            for(int mask1=0; mask1<(1<<m); mask1++)
            {
                int mask=(~mask1)&((1<<m)-1);
                for(int mask2=mask; mask2<(1<<m); mask2=(mask2+1)|mask)
                {
                    if(judge(mask2^mask))
                    {
                        d[i+1][m][mask2]+=d[i][m][mask1];
                    }
                }

            }
        }
    }
    int a,b;
    while(scanf("%d%d",&a,&b),(a||b))
    {
        printf("%I64d\n",d[a][b][(1<<b)-1]);
    }
    return 0;
}


PS:

在网上看到纳米兄的解题报告是用插头DP中的轮廓线解决的,深感不明觉厉。

不过最近确实事情太多,过一段时间好好的学习一下插头DP,再来重做这题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值