「POJ2411」Mondriaan's Dream 题解 (状压DP)
个人觉得\(\mbox{POJ}\)有点玄学(毕竟是北大),勿喷。
题目描述
求把 \(N\times M\) 的棋盘分成若干个 \(1\times 2\) 的长方形,有多少种方案。
分析
首先,如果 \(N\times M\in \{x|x=2k+1,k\in \mbox{Z}\}\) (\(N\times M\) 为奇数),直接下一个。
\(1\times 2\) 的骨牌可以有两种覆盖方法,横着覆盖或是竖着覆盖。
横着放只取决于目前一行的状态,竖着放则依赖于上下两行。
于是我们可以将其表示为二进制,\(0\) 表示这一行这一位是完整的,\(1\) 表示不完整,有缺口,需要上面或下面加一块来补。
例如,对于一个 \(4\times 4\) 的棋盘,第一排可以这么放:
那么第二排就可以这样放:
合起来就是
不难发现,如果第一行某一位状态为 \(1\) ,那么第二行这一位状态只能为 \(1\),如果第一行某一位状态为 \(0\),那么第二行这一位状态可以任选。
不过,第三行怎么放呢?
如果我们将前两行的结果压为第一行,那么前两行的状态就是:
因为前两行已经组成了一个完整的长方形,不缺了。
这个 \(\ 0\ 0\ 1\ 1\)如何得来呢?
不难发现:
那么问题又来了,例如,我们前 \(i\) 行的状态为 \([0\ 1\ 0\ 1]\),第 \(i\) 行的假定状态为 \([1\ 0\ 0\ 1]\),很显然,这个状态对于 \([0\ 1\ 0\ 1]\) 是不合法的。那么我们怎么判断 \([1\ 0\ 0\ 1]\) 状态对于 \([0\ 1\ 0\ 1]\) 不合法呢?
先用 \([0\ 1\ 0\ 1]\ \mbox{xor}\ [1\ 0\ 0\ 1]\) 得到 \([1\ 1\ 0\ 0]\),发现 \([1\ 1\ 0\ 0]\ \mbox{and}\ [0\ 1\ 0\ 1]=[0\ 1\ 0\ 0]\),说明有一个缺口没有将其补上,这时我们就可以判断 \([1\ 0\ 0\ 1]\) 状态对于 \([0\ 1\ 0\ 1]\) 不合法。
状态
\(f_{i,j}\) 覆盖第 \(i\) 行后,第 \(1\) 到 \(i\) 行状态为 \(j\) 的方案数。
目标
\(f_{n,0}\)
状态转移
\(f_{i,j\ \mbox{xor}\ k}+=f_{i-1,k} (\ (j\ \mbox{xor}\ k)\ \mbox{and}\ k\ =\ 0\ )\)
\(j\) 表示当前行状态,\((j\ \mbox{xor}\ k)\ \mbox{and}\ k\ =\ 0\)是保证将前面留下的缺空补上。
注意,\(j\) 必须是合法状态,可以事先处理出来,而 \(k\) 是总状态,必须枚举。
初始状态
\(f_{1,j} = 1\)
\(j\) 是一个合法状态
AC code
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int read(){
int x=0;
char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x;
}
int d[15][405];
int cnt[15];
void prep(){
for(int i=0;i<(1<<11);i++){//预处理合法状态,将010一类不可能的状态排除
int tot=0;
for(int j=0;j<11;j++){
int t=(i>>j)&1;
if(!t)tot++;
if(tot&1){
if(t)break;
continue;
}
if(i>(1<<j+1)-1)continue;
cnt[j+1]++;
d[j+1][cnt[j+1]]=i;
}
}
}
typedef long long ll;
ll f[15][(1<<11)+5];
int main(){
prep();
while(true){
int n=read(),m=read();
if(!n&&!m)break;
if((n&1)&&(m&1)){
puts("0");
continue;
}
memset(f,0,sizeof f);
for(int i=1;i<=cnt[m];i++)f[1][d[m][i]]=1;//初始状态
for(int i=2;i<=n;i++)
for(int j=1;j<=cnt[m];j++)
for(int k=0;k<(1<<m);k++){//枚举前i-1行的状态
if((d[m][j]^k)&k)continue;//排除不补缺口的状态
f[i][d[m][j]^k]+=f[i-1][k];
}
printf("%lld\n",f[n][0]);
}
return 0;
}