Mondriaan's Dream
Squares and rectangles fascinated the famous Dutch painter Piet Mondriaan. One night, after producing the drawings in his 'toilet series' (where he had to use his toilet paper to draw on, for all of his paper was filled with squares and rectangles), he dreamt of filling a large rectangle with small rectangles of width 2 and height 1 in varying ways. Description
Expert as he was in this material, he saw at a glance that he'll need a computer to calculate the number of ways to fill the large rectangle whose dimensions were integer values, as well. Help him, so that his dream won't turn into a nightmare!
Input
The input contains several test cases. Each test case is made up of two integer numbers: the height h and the width w of the large rectangle. Input is terminated by h=w=0. Otherwise, 1<=h,w<=11.
Output
For each test case, output the number of different ways the given rectangle can be filled with small rectangles of size 2 times 1. Assume the given large rectangle is oriented, i.e. count symmetrical tilings multiple times.
Sample Input
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
Sample Output
1
0
1
2
3
5
144
51205
经过简单分析后,这道题我不会,所以暂时就放到了一边,然后去找状压DP相关的文章去看,结果就发现了这道题的模板题--状态压缩动态规划 POJ 2411 (编程之美-瓷砖覆盖地板),所以我觉得有必要来仔细研究一下这道题了。
简单看过文章给的思路后,犹如醍醐灌顶,豁然开朗,恍然大悟,茅塞顿开……所以把这道题作为了我区间DP的进阶题。
这道题的思路肯定不唯一,但是受文章的影响,我的思路也按照他给的思路进行了。
首先我们要对方格的状态进行分析,一块1×2的方格共有横竖两种放法,但是在一行中却又三种状态,
1. 不用放了,可能在(i-1, j)的时刻已经被竖着放上了,然后考虑的是(i, j+1)
2. 横着放,将(i, j+1)也放上了,然后考虑的是(i, j+2)
3. 竖着放,(将i,j)和(i+1,j)放上一个竖立的方格。
所以我们要对这三种状态进行记录,横着放置和不用放置(上一行已经竖着放过了)用1来表示,竖着放用0来表示,这样计数的目的是为了确定状态的兼容性问题,竖放会影响下一行的状态。所以我们可以得出如下图状态
那么我们此时就可以进行状态压缩了,然后我们的状态转移方程是什么呢,或者说上下相邻两行的状态兼容性又是什么呢。
我们可以设DP[i][j]为第i行执行到第j种状态时的可行解数目。
我们要让当前铺设的第i行的状态,来兼容第i-1行已铺设好的状态,所以我们需要知道它们之间的制约关系应该是怎样的,在进行仔细缜密的思考后,可以得出,如果第i-1行的第x位置进行的为竖放(状态为0),那么相应的第i行的x位置一定不用再放了(状态为1)
如果i-1行x位置状态为1,那么它有两种可能的放法,横放或者已被第i-2行放置,所以这时对第i行的放法没有影响,第i行可以竖放(状态为0),也可以横放(状态为1),但是这时如果第i行选择了横放的话,那么相应的第i行的x+1的位置也必定为1,那么这时就会反过来制约第i-1行x+1位置的放法,此时i-1行x+1位置状态也必须为1,因为如果为0的话那么第i行就不能选择横放了,如果第i行选择竖放则没有影响。
对于第一行的兼容性,我们要做一下特别的分析,在第一行中,要么放0, 要么放1。
加入当前测试的是 DP[1][j]的第 x的比特位,即1行,x列
1. 如果x是1,那么 x + 1 也一定是1,然后测试到 x + 2
2. 如果x是0, 那么直接测试下一个 x + 1
补充说明一点,当测试循环中,我们有时候必须要移动 1 位,有时候移动2位,当需要移动2位并且 x == M - 1(M列数)的时候,说明已经不可能兼容了。另外我们还可以最后一行的状态比为全1,因为此时已经没有下一行了,所以我们就不能选择竖放了。
所以我们此时也就不难写出代码了
#include<iostream>
#include<string.h>
using namespace std;
long long dp[12][1<<12];
int h,w;
bool theFirstline(int state,int lastline)
{
int i=0;
int q=(0x1<<w)-1;
if(lastline==0&&state!=q){
return false;
}
while(i<w){
if((state&(0x1<<i))){
if(i==w-1||(state&(0x1<<(i+1)))==0){
return false;
}
i+=2;
}else{
i++;
}
}
return true;
}
bool theOthersline(int prestate,int nowstate,int lastline)
{
int i=0;
int q=(1<<w)-1;
if(lastline==0&&nowstate!=q){
return false;
}
while(i<w){
if((nowstate&(1<<i))==0){
if((prestate&(1<<i))==0){
return false;
}
i++;
}else {
if((prestate&(1<<i))==0){
i++;
}else {
if((i==w-1)||!((nowstate&(1<<(i+1)))&&(prestate&(1<<(i+1))))){
return false;
}
i+=2;
}
}
}
return true;
}
int main()
{
while(cin>>h>>w&&(h||w)){
memset(dp,0,sizeof(dp));
const int allstate=1<<w;
for(int i=0;i<allstate;i++){
if(theFirstline(i,h-1))dp[0][i]=1;
}
for(int i=1;i<h;i++){
for(int ns=0;ns<allstate;ns++){
for(int ps=0;ps<allstate;ps++){
if(dp[i-1][ps]){
if(theOthersline(ps,ns,h-(i+1))){
dp[i][ns]=dp[i][ns]+dp[i-1][ps];
}
}
}
}
}
cout<<dp[h-1][allstate-1]<<endl;
}
return 0;
}