poj 2411 Mondriaan's Dream
一、原题链接以及题目描述
题目传送门
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/df53240e5c7179bc3503fd1d51054c6f.png#pic_center)
二、思路
- 状态压缩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];
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;
}
}