poj 2411 Mondriaan‘s Dream

一、原题链接以及题目描述

题目传送门
在这里插入图片描述

二、思路

  • 状态压缩dp的经典题目,没听说过状态压缩dp问题不是太大,反正就是一种动态规划的一个分支,只要学过动态规划就成。
  • 首先对原问题进行一个转化:向棋盘塞满1×2的方格,自然是有横着放置的,也有竖着放置的。对于每一种固定的方案,当横着的方格放置完毕以后,竖着放置的方格只有一种摆放方案(贴着边界摆放),那么问题就转换成了求解所有摆放横着的方格的合法方案数。
  • 定义f[i,j]:目前已经完成(完成的含义:第一点是不再向前i-1列摆放任何横着的方格,第二点是此时如果往前i-1列放置竖着的方格,是可以把前i-1列都塞满的)了对前i-1列横着的方格的放置(对于放置的说明:横着的1×2方格有左右两个格子,这里指的是第i-1列的某个格子和1×2左边的格子重合了,而1×2方格的右边的那个格子会触及到第i列),j是一个二进制数字,代表的是1×2方格的右边格子触及到第i列的状态:有的话,二进制位是1;反之。
  • 如果棋盘的规模是n行m列的话,最后的答案就是f[m+1,0]:完成了对前m列的摆放,并且伸到第m+1列的二进制表示是零,代表每一位都是零,那就是说,根本没有格子触及第m+1列,符合题目的要求:恰好装满棋盘。
  • 如何进行状态转移?本题只要合理使用上一级的状态即可,想要求f[i,j],就得使用到所有的f[i-1,k]。f[i,j]代表第i-1列的摆放状态是j,能够形成这种摆放方式的有很多种。按照人类的逻辑,那些横着的格子是从左边到右边,一列一列地进行摆放的。f[i-1,k]代表前i-2列的摆放都已经完成,那么只需要在此基础上,继续完成第i-1列的摆放,就可以顺利地转移到f[i,j]。
  • 我们可以去枚举所有的f[i-1,k],看是否可以转移到f[i,j]。
  • 由于前面i-2列已经完成,接下来只要满足第i-1列是合法的即可:我们在摆放第i-1列的时候,如果第i-2列的某一行摆放过格子,那么第i-1列的该行就不可以摆放格子,因为重叠的情况是不合法的;此外,第i-1列的空白处不能出现连续奇数个空格,否则也是不合法的。
  • 然后就是枚举所有的j,将全部第i层的状态都计算出来,为计算第i+1层的状态做准备。

三、代码实现

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
typedef long long LL;
const int N=13,M=1<<11;
LL dp[N][M];
bool judge[M];
//预处理看看哪些二进制数字是连续的零都是偶数
vector<int>store[M];
//store[i]数组存放的是所有可以转移到j的状态
int n,m;
int main()
{
    while(1)
    {
        cin>>n>>m;
        if(n==0&&m==0)
        return 0;
        for(int i=0;i<1<<n;i++)
        {
            int cnt=0;
            bool tmp=1;
            for(int j=0;j<n;j++)
            {
                if(i>>j&1)
                {
                    if(cnt&1)
                    {
                        tmp=0;
                        break;
                    }
                }
                else
                cnt++;
            }
            if(cnt&1)
            tmp=0;
            judge[i]=tmp;
        }
        for(int i=0;i<1<<n;i++)
        {
            store[i].clear();
            for(int j=0;j<1<<n;j++)
            {
                if((i&j)==0&&judge[i|j])
                store[i].push_back(j);
            }
        }
        memset(dp,0,sizeof dp);
        dp[1][0]=1;
        for(int i=2;i<=m+1;i++)
        {
            for(int j=0;j<1<<n;j++)
            {
                for(int k=0;k<store[j].size();k++)
                dp[i][j]+=dp[i-1][store[j][k]];
            }
        }
        cout<<dp[m+1][0]<<endl;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值