您的位置:首页 > 其它

Mondriaan's Dream----状压DP

2017-02-13 19:34 351 查看
Mondriaan's Dream

Time Limit: 3000MS Memory Limit: 65536K
Total Submissions: 16013 Accepted: 9267
Description

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. 



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

Source

Ulm Local 2000

题目链接:http://poj.org/problem?id=2411

其实这篇文章不能算是我的原创,我之所以定义为原创是我会给大犇的代码,好难的DP啊。

大牛链接:http://www.cnblogs.com/scau20110726/archive/2013/03/14/2960448.html

看了大牛的题解我还看了一个下午加一个晚上......

代码:

/*
最上面的为第1行,最下面为第n行
从上到下按行DP
其中一行的状态我们用一个二进制表示,0表示没有被覆盖,1表示被覆盖了
最后得到一个01串,这个串变回十进制就是一个状态
定义状态dp[i][s],表示前i-1行已经放满,第i行的状态为s的方案数
状态转移方程为 dp[i][s]=sum{ dp[i-1][ss] } ,其中状态s与状态ss兼容
这个状态转移方程的内涵在于理解s和ss何为兼容
首先我们约定一个放置方法,就是竖着放的时候,我们暂且将其称为“上凸型摆放”
因为竖放必然占据第i-1行和第i行,我们约定这个方块是属于第i行的,也就是说它凸上去了
那么要在第i行的第j列竖放一个方块的话,第i-1行第j列必须没有方块
也就是说,第i行的放置是受到第i-1行的限制的,反过来说在第i行竖放了方块,也会影响第i-1行的状态
所以这样就可以讲解一下状态转移方程了,前i-2行已经放满了,第i-1行的状态为ss(dp[i-1][ss])
此时在第i行开始放一些方块,放的方法不定,可能横放可能竖放,但是按这个方案放完后
第i-1行刚好被填满,且第i行的状态变为了s,所以不难想到第i-1行的状态ss到第i行的状态s这个转移是唯一的
所以有 dp[i][s]=sum{ dp[i-1][ss] }
最后我们详细讨论一下s和ss在什么情况下是兼容的
1.第i行的第j列为1,第i-1行的第j列为1,这样的话,说明第i行的第j列一定不是竖放而是横放否则会与第i-1行的第j列冲突
所以马上紧接着判断第i行第j+1列,如果是1,那么满足横放的规则,同时也要第i-1行第j+1列也要为1,否则的话这个格子没办法填充,
成立后向左移动两格
不满足上述条件的,就是两个不兼容或者不合法的状态
2.第i行第j列为1,第i-1行第j列为0,那么说明第i行第j列应该竖放并填充第i-1行第j列,成立后向左移动一格
3.第i行第j列为0,说明不放方块,那么第i-1行第j列必须为1,否则没法填充这个格子。若第i-1行第j列也为0,不兼容不合法
(至于第i行第j列这个格子空着干什么,其实就是留出来给第i+1行竖放的时候插进来的)

那么目标状态是什么,就是dp
[maxs],maxs表示全部是1的串,即第n-1行以上全部覆盖满,第n行的状态为maxs,即没有空着的格子,也全部覆盖满了
即整个矩形全部被覆盖满了的状态

最后是第1行的初始化问题,因为约定了“上凸型摆放”,所以第1行是不能竖放方格的,只能横放方格,
每横放一个必定占据两个格子,所以在判断一个状态(那个01串)的时候,连着的1的个数必定为偶数,如果出现了单独的1,说明不合法
*/

#include <cstdio>
#include <cstring>
#define N 15
#define MAX (1<<11)+10

long long dp
[MAX];
long long ans

;
int n,m;

bool init(int s)//初始化第一行
{
for(int k=0; k<m; )
{
if(s & (1<<k))//若是可以放
{
if(k==m-1) return false;//最后一各放不下一个小块
if(s&(1<<(k+1))) k+=2;//横放加2
else return false;
}
else k++;
}
return true;
}

bool ok(int s, int ss)
{
for(int j=0; j<m; )
if(s & (1<<j)) //第i行第j列为1
{
if( ss & (1<<j)) //第i-1行第j列也为1,那么第i行必然是横放
{
//第i行和第i-1行的第j+1都必须是1,否则是非法的
if( j==m-1 || !(s&1<<(j+1)) || !(ss&(1<<(j+1))) ) return false;
else j+=2;
}
else j++; //第i-1行第j列为0,说明第i行第j列是竖放
}
else //第i行第j列为0,那么第i-1行的第j列应该是已经填充了的
{
if(ss&(1<<j)) j++;//已经填充
else return false;
}

return true;
}

void solve()
{
int maxs;//所有的状态数
if(n<m)
{ n=n^m; m=n^m; n=n^m; }
//交换后n是行m是列,m较小,那么状态数也可以相应减少
maxs=(1<<m)-1;
memset(dp,0,sizeof(dp));

for(int s=0; s<=maxs; s++) //枚举第一行所有可能的状态
if(init(s))
{
dp[1][s]=1; //方案数都是1
//printf("%d\n",s);
}

for(int c=2; c<=n; c++) //按行dp
for(int s=0; s<=maxs; s++) //第i行的状态
for(int ss=0; ss<=maxs; ss++) //第i-1行的状态
if(ok(s,ss))
dp[c][s] += dp[c-1][ss];

printf("%lld\n",ans
[m]=ans[m]
=dp
[maxs]);
}

int main()
{
memset(ans,-1,sizeof(ans));
while(scanf("%d%d",&n,&m)!=EOF)
{
if(!n && !m) break;
if(!ans
[m]) //记录状态,已经便利过了
{
printf("%lld\n",ans
[m]);
continue;
}
if(n&1 && m&1) //面积法证明一下,当长或宽都为奇数的话面积为奇数,不可能放下小块
{
ans
[m]=ans[m]
=0;
printf("0\n");
continue;
}
solve();
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: