Corn Fields poj3254

题意:给出一个n行m列的草地,1表示肥沃,0表示贫瘠,现在要把一些牛放在肥沃的草地上,但是要求所有牛不能相邻,问你有多少种放法。


分析:假如我们知道第 i-1 行的所有的可以放的情况,那么对于第 i 行的可以放的一种情况,我们只要判断它和 i - 1 行的所有情况的能不能满足题目的所有牛不相邻,如果有种中满足,那么对于 i 行的这一中情况有 x 中放法。

前面分析可知满足子状态,我们我们确定可以用dp来解决。

但是我们又发现,状态是一种放法,不是我们平常dp的简单的状态,所以要用状态压缩!

但是什么是状态压缩呢?


比如前面的情况,一种放法是最多由12个 0 或者 1 组成的,那么我们很容易想到用二进制,用二进制的一个数来表示一种放法。

定义状态dp【i】【j】,第 i 行状态为 j 的时候放牛的种数。j 的话我们转化成二进制,从低位到高位依次 1 表示放牛0表示没有放牛,就可以表示一行所有的情况。

那么转移方程 dp【i】【j】=sum(dp【i-1】【k】)


状态压缩dp关键是处理好位运算。

这个题目用到了 & 这个运算符。

用 x & (x<<1)来判断一个数相邻两位是不是同时为1,假如同时为 1 则返回一个值,否则返回 0 ,这样就能优化掉一些状态

用 x & y 的布尔值来判断相同为是不是同时为1。


解法一:状压dp、

#include<iostream>
#include<cstdio>
using namespace std;
const int p=100000000;
int n,m,a[1<<14],st[1<<14],dp[14][1<<14];
bool judge1(int x)
{
	return x&(x<<1);
}
bool judge2(int i,int x)
{
	return a[i]&st[x];
}
int main()
{
	int i,j,k=0,f,x,ans=0;
	scanf("%d%d",&n,&m);
	for(i=0;i<n;i++){
		for(j=0;j<m;j++){
			scanf("%d",&x);
			if(!x){
				a[i]+=(1<<j);
			}
		}
	}
	for(i=0;i<(1<<m);i++){
		if(!judge1(i)){
			st[k++]=i;
		}
	}
	for(i=0;i<k;i++){
		if(!judge2(0,i)){
			dp[0][i]=1;
		}
	}
	for(i=1;i<n;i++){
		for(j=0;j<k;j++){
			if(judge2(i,j))  continue;
			for(f=0;f<k;f++){
				if(judge2(i-1,f))  continue;
				if(!(st[f]&st[j])){
					dp[i][j]+=dp[i-1][f];
				}
			}
		}
	}
	for(i=0;i<k;i++){
		ans+=dp[n-1][i];
		ans%=p;
	}
	printf("%d\n",ans);
	return 0;
}

解法二:搜索。

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<cstdio>
using namespace std;
const int maxn=(1<<16);
int m,n;
int ans=0;
int mod=100000000;
int f[30][maxn],a[30];
void dfs(int i,int j,int state,int nex)//state表示当前行状态,
{//nex表示下一行状态
    if(j>=n)
    {
        f[i+1][nex]+=f[i][state];
        f[i+1][nex]%=mod;//这个要取余,防止中间过程溢出.
        return;
    }
    dfs(i,j+1,state,nex);//这一个点不放
    if(!((1<<j)&state)) dfs(i,j+2,state,nex|(1<<j));//这一个点如果能放就放
    return;
}
int main()
{
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++)
    for(int j=0;j<n;j++)
    {
        int x;
        scanf("%d",&x);
        if(x==0)
        a[i]=a[i]|(1<<j);
    }
    f[1][a[1]]=1;//当第一行什么也不放时只有一种方法
    for(int i=1;i<=m;i++)
    for(int j=a[i];j<(1<<n);j++)//这儿从a[i]开始就行了

{//因为放0个就是a[i],没有比它更少了.

        if(f[i][j])
        dfs(i,0,j,a[i+1]);
    }
    for(int i=0;i<(1<<n);i++)
    {if(f[m+1][i]){ans+=f[m+1][i];ans%=mod;}}//注意最后加的是m+1行.
    printf("%d",ans);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值