(动态规划)蒙德里安的梦想--状态压缩dp

 对于这道题目由于只能分割成1*2的小方块,它既可以竖着放也可以横着放

想一下是不是我们把横着放的小方块确定好了之后竖着的小方块就只能一个一个填充进去了

所以要求的方案数就可以转化为小方块可以横着摆放的方案数

状态表示:f(i,j):摆放第i列,i-1列伸出来横着的方格状态为j的方案数,j为一个二进制数,范围是0~行数位数的二进制范围;

j是二进制数这里也就是状态压缩的体现

用0或者1表示有没有i-1列有没有伸出来0表示没有1表示有

仔细想想竖着摆放的话要能摆放进去的话竖直方向上留着的格子数必须是偶数

所以我们需要记录一下哪些j也就是二进制数是满足空间空出来的是偶数

    for(int i=0;i<1<<n;i++)//j的状态数(二进制)
        {
            bool flag=true;//标识表示当前j是否满足中间空出来的是偶数
            int cnt=0;//表示二进制数中0的个数
            for(int j=0;j<n;j++)
            {
                if((i>>j)&1)//如果第j行中是1也就是含有方块
                {
                    if(cnt&1)//判断上一个1到该行为止中间的空出来的个数是不是偶数,不是就进入if
                    {
                        flag=false;
                        break;
                    }
                    cnt=0;//每次碰到一个1就把cnt置为0,其实也可以省略这里方便看懂
                }
                else cnt++;
                if(cnt&1)flag=false;
                st[i]=flag;
            }
        }

接着再想想第i-1列要能伸出来是不是意味着第i-2列没有向i-1列伸出

因为只有两格要是i-2向i-1伸出了那么i-1就不可以向第i列伸出了

动态规划往往是看上一步,既然第i列已经确定了是j

那么就要看看i-2列有没有和它冲突也就是i-2列有没有向i-1列伸出

比如 第i-2列插过来的是k=10101,第i-1列插出去到第i列的是 j =01000

那么合在第i-1列,到底有多少个1呢?
自然想到的就是这两个操作共同的结果:两个状态或。 j | k = 01000 | 10101 = 11101
这个 j|k 就是当前 第i-1列的到底有几个1,即哪几行是横着放格子的

判断它们构成的i-1列满不满足中间空着的是偶数个格子

处理出来先

    for(int i=0;i<1<<n;i++)
        {
            cright[i].clear();
            for(int j=0;j<1<<n;j++)
            {
                if((j&k)==0&&st[j|k])
                {
                    cright[i].push_back(j);
                }
            }
        }

最后每次初始化一下dp用的数组f

想一下对于f[i][j]的定义

前i-1列已经摆好且伸出状态时j的集合数

所以f[0][0]首先没有-1列其次没有伸出来的也就是0意味着第0列全都是竖着摆放的

这也算是一种状态所以初始化f[0][0]=1;

其他的f[0][j]=0;不用管

#include<iostream>
#include<cstring>
#include<vector>
using namespace std;

const int N=12,M=1<<N;

long long f[N][M];

vector<vector<int>>cright (M);
bool st[M];
int n,m;
int main()
{
    while(cin>>n>>m,n&&m)
    {
        for(int i=0;i<1<<n;i++)//求存储0~1<<n中中间空出来的方格是偶数的,是的话st就是true反之false;
        {
            int cnt=0;
            bool is_validity=true;
            for(int j=0;j<n;j++)
            {
                if((i>>j)&1)
                {
                    if(cnt&1)
                    {
                        is_validity=false;
                        break;
                    }
                }
                else cnt++;
            }
            if(cnt&1)is_validity=false;
            st[i]=is_validity;
        }
        //下面来看进一步的判断:看第i-2列伸出来的和第i-1列伸出去的是否冲突
        for(int j=0;j<1<<n;j++)
        {
            cright[j].clear();
            for(int k=0;k<1<<n;k++)
            {
                if((j&k)==0&&st[j|k])//判断j代表的二进制数以及k代表的二进制数没有在同一行的
                //以及他们的中间i-1是由j和k组成,判断是不是满足st,也就是中间空的格子为偶数
                {
                    cright[j].push_back(k);
                }
            }
        }
        memset(f,0,sizeof f);
        f[0][0]=1;//伸出到0的列的显然是不存在的也就是0所以只有是竖着摆放的
        
        for(int i=1;i<=m;i++)
            for(int j=0;j<1<<n;j++)
                for(auto x:cright[j])
                    f[i][j]+=f[i-1][x];
                    
        cout<<f[m][0]<<endl;
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值