poj 2411 Mondriaan's Dream (状态压缩dp)

题目链接:哆啦A梦传送门

题意:给出n*m的方块,让你用1*2的方块去填满它,问:有多少种不同的方案?

题解:

参考链接:https://blog.csdn.net/u014634338/article/details/50015825

看完题解,大概就这几个核心点。

1,我们用二进制来表示横放和竖放,如图所示:

2,我们已经知道横放和竖放的规则,那么我们只需枚举二进制,判断第i+1行是否跟第i行产生不兼容。

首先我们先初始化第1行。

然后我们可以分类下判断兼不兼容的情况:

(i+1,j) (表示第i+1行,j列)

(i+1,j)为1时,(i,j)为0,满足条件(竖放)

                           (i,j) 为1,必须(i+1,j+1)和(i, j+1) 必须为1才满足条件(两个横放)

(i+1,j)为0时,(i,j)为1,满足条件(横放与竖放)

其它情况都为不兼容。

 

代码:

#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;

typedef long long LL;

LL dp[15][1<<12];
int n,m;

///设置初始状态
bool init(int status)
{
    for(int j=0;j<m;) ///前j-1列符合要求,对第j列进行判断
    {
        if(status&(1<<j)) ///第j列为1
        {
            if(j==m-1) return false; ///j为最后一列
            if(status&(1<<(j+1)))///第j列和第j+1都为1,则表示横放,可行,考虑j+2列
                j+=2;
            else   ///第j列为1,j+1列为0,不可行
                return false;
        }
        else j++; ///第j列为0,则为竖放,可行
    }
    return true;
}


///判断上一次的状态和本次状态是否兼容
bool check(int now,int pre)
{
    for(int j=0;j<m;)
    {
        if(now&(1<<j)) ///第i行第j列为1
        {
            if(pre&(1<<j)) ///第i-1行第j列也为1,那么第i行必然是横放
            {
                ///第i行和第i-1行的第j+1列都必须是1,否则是非法的
                if(j==m-1||!(now&(1<<(j+1)))||!(pre&(1<<(j+1))))
                    return false;
                else j+=2;
            }
            else j++; ///第i-1行第j列为0,说明第i行第j列是竖放
        }
        else{ ///第i行第j列为0,那么第i-1行的第j列应该是已经填充了的,必须为1
            if(pre&(1<<j)) j++;
            else return false;
        }
    }
    return true;
}

void solve()
{
    int tot=(1<<m)-1;

    memset(dp,0,sizeof(dp));

    for(int i=0;i<=tot;i++){ ///初始化第1行
        if(init(i))
            dp[1][i]=1;
    }

    for(int i=2;i<=n;i++)
    {
        for(int j=0;j<=tot;j++)  ///第i行的状态
        {
            for(int k=0;k<=tot;k++) ///第i-1行的状态
            {
                if(check(j,k))
                    dp[i][j]+=dp[i-1][k];
            }
        }
    }

    printf("%lld\n",dp[n][tot]);
}

int main()
{

    while(~scanf("%d%d",&n,&m))
    {
        if(n==0&&m==0) break;

        if(n&1&&m&1) { ///都为奇数
            printf("0\n");continue;
        }

        if(m>n) swap(n,m); ///交换下,使得m较小,减少状态量

        solve();
    }
    return 0;
}

 

总结下:这里用二进制来表示横放与竖放,就用得很巧妙,我们要想要用状态压缩去做题,必须要找到适合题目要求的独特二进制表示,

这里横放用1 1 ,竖放用0|1 ,就是独特的二进制来表示,之后我们就去枚举,看是否冲突。

 

我的标签:做个有情怀的程序员。

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值