首先蒙德里安问题是求解所有横竖1x2小长方形的摆放情况个数,显然当我们摆放完横着的小长方形之后,竖着的小长方形的摆放情况唯一,即只需要求出摆放横小长方形的合法方案数即可。
那么,如何摆放横小长方形才能合法呢?我们可以把棋盘看成一张图,被占用的格子属性记为1,没被占用的则记为0。
那么想要让方案合理,显然每一列的连续的0必须是偶数个的,因为如果是奇数个的空白格子,1x2的竖小长方形是无法填充完的。这就是第一个预处理。
对于第二个预处理,我们必须使横着的1x2小长方形互相分离,不能有重叠部分,因此我们需要用到状态压缩DP中的状态函数来模拟所有横1x2小长方形的摆放情况,我们设f[a][b]意为前a-1列已经填充完毕情况下第a列的情况(将该情况记为b)所对应的方案数量。同时我们记k为j的前一列,第二个预处理就是保证j和k不会在同一行摆放横小长方形。
既然已经知道了合法方案的预处理判定,接下来只需要进行DP,状态更新然后输出即可
上代码:
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
const int N=12,M=1<<12;
long long f[N][M];
bool st[M];
vector<int> states[M];
int m,n;
int main(){
while(cin>>n>>m,n||m){
//预处理1:预处理st,每列不能有奇数个0
for(int i=0;i<1<<n;i++){
int cnt=0; //记录连续个0
bool flag=true;
for(int j=0;j<n;j++){
if((i>>j)&1){
if(cnt&1){ //判断两个1区间内的情况是否合法
flag=false;
break;
}
cnt=0;
}
else cnt++;
}
if(cnt&1) flag=false;
st[i]=flag;
}
//预处理2:预处理states,找出所有合法情况
for(int j=0;j<(1<<n);j++){
states[j].clear();
for(int k=0;k<(1<<n);k++)
if((j&k)==0&&st[j|k]) //即j,k不重叠且0个数不为奇数
states[j].push_back(k); //意为当第i列情况为j时,第i-1列情况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:states[j])
f[i][j]+=f[i-1][k];
}
cout<<f[m][0]<<endl;
}
return 0;
}