「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\ (12) \]

那么第二排就可以这样放:

\[1\ 1\ 1\ 1\ (15) \]

合起来就是

\[1\ 1\ 0\ 0\ (12) \]

\[1\ 1\ 1\ 1\ (15) \]

不难发现,如果第一行某一位状态为 \(1\) ,那么第二行这一位状态只能为 \(1\),如果第一行某一位状态为 \(0\),那么第二行这一位状态可以任选。

不过,第三行怎么放呢?

如果我们将前两行的结果压为第一行,那么前两行的状态就是:

\[0\ 0\ 1\ 1\ (\ 3\ ) \]

因为前两行已经组成了一个完整的长方形,不缺了。

这个 \(\ 0\ 0\ 1\ 1\)如何得来呢?

不难发现:

\[[1\ 1\ 0\ 0]\ (12)\ \mbox{xor}\ [1\ 1\ 1\ 1]\ (15)\ = [0\ 0\ 1\ 1]\ (3) \]

那么问题又来了,例如,我们前 \(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;
}

$$-----EOF-----$$

posted @ 2022-02-12 12:03  AlienCollapsar  阅读(263)  评论(0编辑  收藏  举报
// 生成目录索引列表 // ref: http://www.cnblogs.com/wangqiguo/p/4355032.html // modified by: zzq