PKU 3254 状态压缩DP

http://poj.org/problem?id=3254

题意:输入一个n*m的矩阵,矩阵由数字0和1组成,0表示不能种植玉米的草地,1表示能够种植玉米的草地。然后在种植了玉米的草地上放牛,放牛的数目随意,但是要满足相邻的草地间不能同时放牛,要求所有放牛的方案总数。

例如:

2 3
1 1 1
0 1 0

方案总数为9,因为将每块草地编号后得到
1 2 3
0 4 0

一头都不放有1种情况,放一头牛的方案为1,2,3,4都行,有4种,放两头牛的方案有(1,3) (1 4) (3 4),有3中,放三头牛的方案有(1,3,4)一种,一共9种!

分析:地图显然是0和1构成,即用0,1来表示所有的情况,所以显然是二进制的情况,即用二进制来表示所有情况的一种题型。

我们可以使用二进制的状态压缩,用一个数来表示某一行的放牛情况,例如某一行的放牛方案为5,而五的二进制表示为1 0 1,对于上面的数据来说,相邻的没有放牛,并且放牛的地方都有种植玉米,所以这是一种可行的方案。那么第一行到底有多少可行的方案呢,由于相邻的玉米间不许放牛,不难看出有

(0,0,0)[0] 、 (1,0,0)[4] 、 (0,1,0)[2] 、 (0,0,1)[1] 、 (1,0,1)[5]

再看第二行,它的所有方案为

(0,0,0)[0] 、 (0,1,0)[2]

现在状态压缩已经完毕,我们要开始动规,动规是从第一行一直到最后一行。

我们开设数组dp[i][j],表示第i行取第j种状态的所有可行方案总数。

看上面的数据,第一行取0,4,2,1,5五种状态中的一种都只有一种情况,所有dp[1][i]=1.(i=1,2,3,4,5)。

再看第二行的第一种状态0,如果第二行取了0的话,也就是说第二行不放牛,那么第二行和第一行的任何一种状态都不矛盾(上下相邻的不能放牛),那么第一行的所有情况都可以取到,于是dp[2][1]=5。

再看第二个状态2.,如果取了2的话,那么第二行将和第一行的状态2发生矛盾,即放牛情况为

0 1 0

0 1 0

很显然的看到上下相邻了,所以第二行的状态2只能和第一行的状态0,4,1,5共存,即dp[2][2]=4。

最后我们将最后一行的所有状态的dp值相加,便得到了我们要求的答案~~dp[2][1]+dp[2][2]=5+4=9

View Code
#include<iostream>
#include
<string>
#include
<vector>
using namespace std;

int n,m;
int dp[13][2048];
vector
<int>num[13];

void change(int i,int temp) //得到第i行的所有可行状态
{
int j;
for(j=0;j<(1<<m);j++)
{
if((j<<1)&j || (j>>1)&j)//如果状态j有两个相邻的1,则略过
continue; //位运算优先级别很低,一定要用括号
if(j&temp) //如果j和temp(即草地情况)相与不为0,说明在不能种植玉米的地方放牛了,略过
continue;
num[i].push_back(j);
//最后的合格
}
}

int main()
{
int i,j,temp,a,k;
freopen(
"D:\\in.txt","r",stdin);
while(scanf("%d%d",&n,&m)!=EOF)
{
for(i=1;i<=n;i++)
{
num[i].clear();
temp
=0;
for(j=1;j<=m;j++) //将某一行草地转变为二进制数,这里将草地进行了翻转
{ //即1表示不能放牛,0表示能够放牛,因为可以放牛的话,放(1),不放(0)与0(草地)相与的话都是0
scanf("%d",&a);//而1(草地)与放(1)相与则会等于1,这样的转换便于判断某个状态是否可行
a=1-a;
temp
=temp*2+a;
}
change(i,temp);
}
memset(dp,
0,sizeof(dp));
for(i=0;i<num[1].size();i++)
{
dp[
1][i]=1;
}
for(i=2;i<=n;i++)
{
for(j=0;j<num[i].size();j++)
{
for(k=0;k<num[i-1].size();k++)
{
if((num[i][j])&(num[i-1][k]))
continue;
dp[i][j]
+=dp[i-1][k];
}
}
}
int ans=0;
for(i=0;i<num[n].size();i++)
{
ans
=(ans+dp[n][i])%100000000;
}
printf(
"%d\n",ans);
}
return 0;
}

方法二:

上面的方法非常直观,并且由于地图已经明确给出,所以每一行的所有可行状态也很容易找出,下面介绍一种不需要预处理所有可行状态的方法,这个方法是在上一层可行的方法的前提下采用递推的方式找出下一行的所有可行解,并DP,直到最后一行为止。关于这题,假如我们一行的状态都不知道,那么如何开始呢?其实,我们只需要在第一行草地的前面人工的加上第零行,第零行一头牛都不放,这样的方式有且只有一种方案,即dp[0][0]=1。而且我们很容易看出,第零行的这种状态不会影响到下面的结果。

接下来我们采用深搜的方式根据上一行的可行解递推寻找每一行的可行解,假设草地的宽度是W,上一行的状态是j,这一行的状态是state。对宽度从右向左的进行一次遍历,如果在某一个位置上,上一行中j没有放牛,并且state在该位置的邻位上也没有放牛,并且地图允许放牛,那么便跟新state,使它在该位置上的值变为1。当遍历到了最左边时,该行的一个可行state便得到了,这时dp[i+1][state]+=dp[i][j];

View Code
#include<iostream>
#include
<string>
using namespace std;
#define maxn (1<<12)


int map[14][14];
int dp[14][maxn+2];
int n,m,i,j;

void dfs(int k,int state) //所有状态顺序都是从右往左,即k的增加说明了状态在向左进位
{
if(k==m)
{
dp[i
+1][state]+=dp[i][j];
return;
}
if(!map[i+1][m-k-1]) //没有玉米
{
dfs(k
+1,state); //状态保持不变
}
else
{
if(((j>>k)&1)==0)
{
if(k==0)
dfs(k
+1,state|1<<k); //该位置k放了牛,则state在k位上加1
if(k-1>=0 && ((state>>(k-1))&1)==0) //并且它右边相邻的地里没有放牛
dfs(k+1,state|1<<k); //同上
}
dfs(k
+1,state);
}
}

int main()
{
freopen(
"D:\\in.txt","r",stdin);
while(scanf("%d%d",&n,&m)!=EOF)
{
for(i=1;i<=n;i++)
{
for(j=0;j<m;j++)
{
scanf(
"%d",&map[i][j]);
}
}
memset(dp,
0,sizeof(dp));
dp[
0][0]=1; //地图是从1到n,这里人工的将地图扩大一行,第零行一个玉米都不种,这样便有了初始值,又不影响接下来的搜索
for(i=0;i<n;i++) //注意i,j是全局变量,j便是第i行的状态
{
for(j=0;j<(1<<m);j++)
{
if(dp[i][j])
{
dfs(
0,0); //对i+1行进行DP,可达的所有状态dp[i+1][j]都将加上dp[i][j]
}
}
}
int ans=0;
for(i=0;i<(1<<m);i++)
{
ans
=(ans+dp[n][i])%100000000;
}
printf(
"%d\n",ans);
}
return 0;
}
posted @ 2011-08-11 23:37  Accept  阅读(1182)  评论(0编辑  收藏  举报