状压dp入门题目
本题有多种摆法,我们只需要考虑横着摆放的长方形满足要求后,竖着放的只有1种符合要求,根据乘法原理,总摆放数就是
横着摆放的种数*1=总摆放个数
也就是说,我们如果能求出所有横着摆放的合法种数,就能求出最后答案
如何表示横着摆放的种数?(状态压缩)
可以用一个二进制数表示
我们考虑第i列的情况,第i列如果被i-1列横着的长方形占有,就将当前位置设为1,否则,设为0,如下图所示
上面就是我们的状态压缩表示
如何进行dp?
本题的dp分析图如下
总结:要想得到f(i,j)的状态,由于i-1列的状态已经确定,需要利用第i-1的所有状态来计算第i列的所有情况的结果
如何判断状态的合法性?
我们的状态是只考虑横着摆放的,但是,也需要考虑竖着放能不能放的下
我们只需要考虑,当前列竖着放能不能填充的满即可
如果当前列连续空着的格子全为偶数,则此状态合法,否则非法
画图解释
预处理:存储所有状态是否合法
换句话说,要满足什么条件,才能进行状态转移?
我们这里需要两次预处理
第一次,需要一个布尔类型的数组,存储当前状态连续空白的数量是否为偶数
bool st[M];//这里的M表示状态总数,是个二进制数
它负责统计,当前状态的连续空白数是否全部为偶数
如果有一段连续空白不是偶数,其值为false
第二次预处理(较难)需要考虑两列的状态
我们这里需要一个二维数组state
vector<vector<int>>state(M);
思考一下,两列状态组合起来,哪些是不合法的?
首先,不能出现冲突,画图解释
观察一下上图,如果两个状态,两个位置都为1(红和黄),就会造成上图的冲突(绿色填充),所以,这个状态不合法,需要排除
还有一种情况,就是两个状态组合起来,连续空白数是否还为偶数?
仍然先画图
观察i-2列的状态(红,11000)和第i-1列的状态(绿,00100),我们发现,它们在第i-2列有公共部分,我们这里就需要判断,将它们组合起来的空白,是否还满足连续空白数为偶数,也就是能塞下一个竖着的长方形?
如果不能,则需要排除
2.2的预处理数组含义为:设当前遍历到state[j],那么state[j]这个数组的所有元素k1…kn就为所有j和k组合还能合法的序列
参考题解
非常感谢!
代码实现
#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;
}