蒙德里安的梦想
代码
注意
1.二进制数表示的意义:
- 1表示有长方形从当i列的某行延伸出去,即横放
- 0表示i-1列某行有长方形延伸过来或者竖放
2.有n行,就有2**n种状态。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=12,M=1<<N;
long long f[N][M];
bool ok[M];
int main()
{
int n,m;
while(cin>>n>>m,n||m)
{
for(int i=0;i<1<<n;i++)
{
int cnt=0;
ok[i]=true;
for(int j=0;j<n;j++)
{
if(i>>j&1)
{
if(cnt&1)
{
ok[i]=false;break;
}
}
else cnt++;
}
if(cnt&1)ok[i]=false;
}
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(int k=0;k< 1<<n;k++)
{
if((k&j)==0&&ok[k|j])f[i][j]+=f[i-1][k];
}
cout<<f[m][0]<<'\n';
}
return 0;
}
解析
1.
由于结果数据可能很大,因此使用long long
const int N=12,M=1<<N;
long long f[N][M];
bool ok[M];
2.
预处理,记录哪些状态是合法的。具体地,一个状态,可以用就是二进制表示。对于二进制上的每一位,如果为1,则表示这一格被占用;反之则是空格。如果空格连续出现,且连续的数目是奇数(只出现一个也不合法),那么竖放一个长方形肯定放不下——不合法。
条件:
for(int i=0;i<1<<n;i++)
{
int cnt=0;
ok[i]=true;
for(int j=0;j<n;j++)
{
if(i>>j&1)
{
if(cnt&1)
{
ok[i]=false;break;
}
}
else cnt++;
}
if(cnt&1)ok[i]=false;
3.
下面代码用于状态是否合法,一共两个条件:
- j&k=0 意味着相邻的两列没有重叠(对于j,k的二进制表示中,如果它们的x位上的数都为1,则意味着有两个长方形重叠,反之则没有重叠)
- ok[k | j]==true j|k的结果表示将j,k被合并后的状态。之后需要做的,就是判断合并后的状态是否存在连续的零,并且其数量是奇数。
if((k&j)==0&&ok[k|j])f[i][j]+=f[i-1][k];
4.
f[m][0]是输出结果。它意义是,对于nXm的空间的m列,我需要这一列没有延伸出去的长方形,也就是这一列没有长方形横跨m和m+1列,也即是说,这一列是由被其它方式填满的——要么竖放,要么上一列横放长方形导致长方形延伸至m列上。
cout<<f[m][0]<<'\n';