您的位置:首页 > 其它

POJ2411 Mondriaan's Dream (轮廓线动态规划典型例题 以及用状态压缩逐行深搜推的方法)

2017-07-27 22:07 423 查看
题目链接:http://poj.org/problem?id=2411
参考博客:http://blog.csdn.net/u013480600/article/details/19499899

给出一个n*m的矩形,然后用1*2大小的多米若骨牌去填充n*m的这个矩形,问有多少种填充方法。
分析:典型的轮廓线动态规划题目。
首先本题目是以一个一个的格子为基础来计算状态的,即每次都是考虑当前位置的格子如何放左上骨牌(以当前位置为最右下角,即只不放,左放,和上放3种情况,没有右放和下放)。且本题的状态都是一条一条的轮廓线。如图:
1
1
1
1
1
1
1
K4
K3
K2
K1
K0
O
 
 
 
 
 
 
 
令<K3K2K1K0O> 为这5个数字对应的二进制值的十进制结果。K3K2K1K0O=11110表示由这5个格构成的轮廓线状态 如

K4 K3 K2 K1 K0 O 分别为: 0 1 1 1 1 0 时,
<K3K2K1K0O> = 30,<K4K3K2K1K0> = 15

本题的状态为d[cur][<K3K2K1K0O>]表示 当前以O格为末尾的轮廓线状态为<K3K2K1K0O>的值对应二进制表示时且K3之前的所有格子的值都为1时构成上图所给状态的方法总数。其中
当前轮廓线为K3K2K1K0O,前一个轮廓线为K4K3K2K1K0。当前的轮廓线状态数用d[cur][ <K3K2K1K0O>]表示,前一个轮廓线状态数用d[1-cur][ <K4K3K2K1K0>]表示。

现在初始时 K4K3K2K1K0O的值对应为 011110.
O格的每一种操作(不放,左方,上方)都会联系前后两种不同的轮廓线。
假设在O格放上骨牌,K4和O值都变为1,则d[cur][<K3K2K1K0O>] =d[cur][<11111>]  +=d[1-cur][<K4K3K2K1K0>]=d[1-cur][<01111>] 这句话的意义是 在O格放上骨牌,则以O格为末尾的轮廓线为11111,且已放的格子情况为:
1
1
1
1
1
1
1
1
1
1
1
1
1
 
 
 
 
 
 
 
 
 
 
 
 
的时候有d[1-cur][<01111>](这是一个整数值)这么多种情况是通过在轮廓线<K4K3K2K1K0>=<01111>的基础上,放O格上骨牌而产生来的。
假设在O格不放,K4的值不改变依然为0,如果执行:
d[cur][<K3K2K1K0O>] =d[cur][<11110>]  +=d[1-cur][<K4K3K2K1K0>]=d[1-cur][<01111>] 这样是非法的。因为不放O格,K4的值永远为0(K4的值如果为0只能通过O格放上骨牌来置1,O格之后的格子无论怎么放骨牌都不会改变K4的值),所以如果执行上述非法操作,违反了关于d[cur][<K3K2K1K0O>]的定义(要求K3之前所有格子的值都为1).
 
假设在O格放左骨牌,也是非法操作,因为O格左边已经是1了,不可能放的下左骨牌。
且在首行不能上放,在首列不能左放。
 
最后当循环处理到第(n,m)格时,轮廓线为<11111>时表示轮廓线前面的n-1行也全是11111,所以最终结果为d[cur][<11111>]。
并且初始值为d[0][<11111>]=1,其他d[0][其他值]=0(其实这个可以不弄,因为首行不能上放,所以首行轮廓线状态数不可能会引用到d[0][01111]这样的初值。)。
 
AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
using namespace std;
#define maxn 15
int n,m,cur;
long long dp[2][1<<maxn];//存就状态和新状态
void update(int a,int b)//a是包含m位二进制数的老状态,b是包含m+1位二进制数的新状态
{
if(b&(1<<m))////判断新轮廓线首位 只有新轮廓线首位为1时才更新
dp[cur][b^(1<<m)]+=dp[1-cur][a];//b^(1<<m)是将b状态的首位变为0 转化成包含m为的状态
}
int main()
{
while(~scanf("%d%d",&n,&m)&&n&&m)
{
memset(dp,0,sizeof dp);
cur=0;
dp[cur][(1<<m)-1]=1;//状态1表示该点已经被放置 0表示为被放置
//这步的目的上让第一行不能让上矩形
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
{
cur=1-cur;
memset(dp[cur],0,sizeof dp[cur]);
for(int k=0;k<(1<<m);k++)//k的二进制形式表示前一个格子的轮廓线状态
{
update(k,k<<1);//当前格不放,直接k左移一位就表示带m+1位的新轮廓线的状态
if(i&&!(k&(1<<(m-1))))//判断是否为第一行  判断旧轮廓线首位是否为0,为0才可以上方
update(k,(k<<1)^(1<<m)^1);//因为要上放,所以对新轮廓线的首尾均更新为1
if(j&&!(k&1))//判断是否为第一列  判断旧轮廓线尾位是否为0,为0才可以往左放
update(k,(k<<1)^3);//因为要左放,所以对新轮廓线的末尾两位更新为1
}
}

printf("%lld\n",dp[cur][(1<<m)-1]);//当最后一行的状态均为1时 满足整个矩阵都已被放满
}
return 0;
}

假设第一行已经填满,则第二行的摆设方式,只与第一行对第二行的影响有关。同理,第三行的摆设方式也只与第二行对它的影响有关。那么,使用一个长度为N的二进制数state来表示这个影响,例如:4(00100)就表示了图上第二行的状态。
因此,本题的状态可以这样表示:
dp[i][state]表示该填充第i行,第i-1行对它的影响是state的时候的方法数。i<=M,0<=state<2N
对于每一行,情况数也有很多,但由于N很小,所以可以采取搜索的办法去处理。对于每一行,搜索所有可能的放木块的情况,并记录它对下一行的影响,之后更新状态。状态转移方程如下:
dp[i][state]=∑dp[i-1][pre]每一个pre可以通过填放成为state
对于每一列的深度优先搜索
AC代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
#define maxn 12
int n,m;
long long dp[maxn][1<<maxn];
void dfs(int i,int j,int old,int nex)
{
if(j>m)
{
dp[i+1][nex]+=dp[i][old];
return ;
}
if(1<<(j-1)&old)dfs(i,j+1,old,nex);
if((1<<(j-1)&old)==0)dfs(i,j+1,old,nex|1<<(j-1));
if (j+1<=m && ((1<<(j-1))&old)==0 && ((1<<(j))&old)==0)
dfs(i,j+2,old,nex);
return ;
}
int main()
{
while(~scanf("%d%d",&n,&m)&&n&&m)
{
memset(dp,0,sizeof dp);
dp[0][0]=1;
for(int i=0;i<n;i++)
{
for(int j=0;j<(1<<m);j++)
{
if(dp[i][j])
dfs(i,1,j,0);
}
}
printf("%lld\n",dp
[0]);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: