[状压dp]经典题:蒙德里安的梦想

原题传送门
本人原文网址
在这里插入图片描述

本题有多种摆法,我们只需要考虑横着摆放的长方形满足要求后,竖着放的只有1种符合要求,根据乘法原理,总摆放数就是

横着摆放的种数*1=总摆放个数
也就是说,我们如果能求出所有横着摆放的合法种数,就能求出最后答案

如何表示横着摆放的种数?(状态压缩)

可以用一个二进制数表示

我们考虑第i列的情况,第i列如果被i-1列横着的长方形占有,就将当前位置设为1,否则,设为0,如下图所示

蒙1.png

上面就是我们的状态压缩表示

如何进行dp?

本题的dp分析图如下

蒙2.png

总结:要想得到f(i,j)的状态,由于i-1列的状态已经确定,需要利用第i-1的所有状态来计算第i列的所有情况的结果

如何判断状态的合法性?

我们的状态是只考虑横着摆放的,但是,也需要考虑竖着放能不能放的下

我们只需要考虑,当前列竖着放能不能填充的满即可

如果当前列连续空着的格子全为偶数,则此状态合法,否则非法

画图解释

蒙3.png

预处理:存储所有状态是否合法

换句话说,要满足什么条件,才能进行状态转移?

我们这里需要两次预处理
第一次,需要一个布尔类型的数组,存储当前状态连续空白的数量是否为偶数

bool st[M];//这里的M表示状态总数,是个二进制数

它负责统计,当前状态的连续空白数是否全部为偶数
如果有一段连续空白不是偶数,其值为false

第二次预处理(较难)需要考虑两列的状态

我们这里需要一个二维数组state

vector<vector<int>>state(M);

思考一下,两列状态组合起来,哪些是不合法的?
首先,不能出现冲突,画图解释

蒙4.png

观察一下上图,如果两个状态,两个位置都为1(红和黄),就会造成上图的冲突(绿色填充),所以,这个状态不合法,需要排除

还有一种情况,就是两个状态组合起来,连续空白数是否还为偶数?

仍然先画图

蒙5.png

观察i-2列的状态(红,11000)和第i-1列的状态(绿,00100),我们发现,它们在第i-2列有公共部分,我们这里就需要判断,将它们组合起来的空白,是否还满足连续空白数为偶数,也就是能塞下一个竖着的长方形?

如果不能,则需要排除

2.2的预处理数组含义为:设当前遍历到state[j],那么state[j]这个数组的所有元素k1…kn就为所有j和k组合还能合法的序列

参考题解

AcWing 291. 蒙德里安的梦想

非常感谢!

代码实现
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long LL;

const int N=12,M=1<<N;
LL f[N][M];//状态方程
bool st[M];//存储每个状态是否满足连续空白数全为偶数
vector<vector<int>>state(M);//存储j和k的组合合法的所有序列
int n,m;

int main()
{
    while(cin>>n>>m &&(n||m))
    {
        //第一步预处理,看看有没有连续的奇数个0
        //二进制表示法:1表示有方块,0表示无方块
        
        for(int i=0;i<(1<<n);i++)//枚举从00000到11111的所有状态,并检查连续0的个数是否为偶数
        {
            int cnt=0;
            for(int j=0;j<n;j++)
            {
                if((i>>j)&1)
                {
                    if(cnt%2==1)
                    {
                        st[i]=false;
                        break;
                    }
                    cnt=0;
                }
                
                else
                {
                    cnt++;
                }
            }
            
            if(cnt%2==1) st[i]=false;
            else st[i]=true;
        }
        
        for(int j=0;j<(1<<n);j++)
        {
            state[j].clear();//多组输入,所以需要清空上一次的数据
            for(int k=0;k<(1<<n);k++)
            {
                if((j&k)==0&&st[j|k])//没有矛盾且它们的组合没有奇数个0
                //st[j|k]另外详细解释一下,j|k是所有位取或,//也就是两者被占有的格子(设为1)全部组合起来//,才是它们两个状态的组合,并判断这个组合是//否满足有偶数个连续0
                //
                {
                    state[j].push_back(k);//记录j状态的其中一个合法组合k
                }
            }
        }
        
        //开始进行dp
        
        memset(f,0,sizeof f);
        
        f[0][0]=1;
        for(int i=1;i<=m;i++)//枚举每一列
        {
            for(int j=0;j<(1<<n);j++)//每一个状态
            {
                for(auto k:state[j])//遍历可行的所有组合方案
                {
                    f[i][j]+=f[i-1][k];//由于是计数,所以相加//所有状态  
                }
            }
        }
        cout<<f[m][0]<<endl;//m-1列全部拼完且没有伸到第m列
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东条希尔薇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值