【ybtoj高效进阶5-5-1】种植方案 / 【luogu P1879】[USACO06NOV] Corn Fields G

【ybtoj高效进阶5-5-1】种植方案 /

【luogu P1879】[USACO06NOV]Corn Fields G

题目大意:

给你一个n*m的矩阵,上面有一些点不可以放东西,其余的点随意。
问取模后总共有多少种方案?

思路:

一看到这种阴间的东西就像是状压DP。
我们设 f [ i ] [ j ] f[i][j] f[i][j] 表示处理到第 i 行,此时的状态为 j 时的方案数
很明显,根据动态规划的惯例,当前行的状态是由上一行转移下来的。
那么当现在的状态合法时,我们有 f [ i ] [ j ] = ∑ f [ i − 1 ] [ k ] f[i][j]=\sum f[i-1][k] f[i][j]=f[i1][k]
那么最后的答案就是 ∑ f [ m ] [ i ] \sum f[m][i] f[m][i]
但是我们怎么判断是否合法呢?
首先,同一行中相邻的两块肥沃的土地不能同时选择,那么对于一个状态 i ,如果它合法,那么就有 i    a n d   ( i > > 1 ) = 0    i    a n d    ( i < < 1 ) = 0 i ~~ and ~(i>>1)=0 ~~i~~and~~(i<<1)=0 i  and (i>>1)=0  i  and  (i<<1)=0
这个为什么是对的呢?
首先看第一个, i>>1 表示 i 把最后一位去掉,每一位向右移了一位,那么你原来是1的位就会改变为0 (当合法的时候,1的左右两边只能是0) 那么每一位都不会对应,所以与运算的结果是0,反之状态不合法。
那么 i<<1 同理。
所以我们可以用一个数组 pd 记录每一个状态是否合法,当然,还需要一个 val 数组来记录每一行的土地状态 (并不是使用状态!!!)。
那么我们怎么判断是否有草种在贫瘠的土地上呢 ?
先给出来: j    a n d    v a l [ i ] = j j ~~ and~~val[i]=j j  and  val[i]=j
来证明一下正确性:
我们发现只有 1    a n d    0 = 0 1~~and~~0=0 1  and  0=0 的时候原数才会出现变化, 而这种情况正是有草出现在贫瘠土地的情况!!0&1 时肥沃土地不种草,合法;0&0 时贫瘠土地不种草,合法;1&1时肥沃的土地种草,合法。
那么我们来到了最后一步:上下两行转移的条件
设当前状态是 j ,上一行状态是 k ,那么 j    a n d   k = 0 j ~~and~k =0 j  and k=0,很明显两边的 “1”不能出现在同一位上,那么与运算的结果自然是 0 了。
上代码。

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#define r register
#define rep(i,x,y) for(r ll i=x;i<=y;++i)
#define per(i,x,y) for(r ll i=x;i>=y;--i)
using namespace std;
typedef long long ll;
const ll V=(1<<12)+10,p=1e8;
ll a[20],val[20],f[20][V],field[12][12];
bool pd[V];
ll m,n;
int main()
{
	scanf("%lld%lld",&m,&n);
	rep(i,1,m)
	 rep(j,1,n)
	  scanf("%lld",&field[i][j]);
	rep(i,1,m)
	 rep(j,1,n)
	  val[i]=(val[i]<<1)+field[i][j]; //每一行的状态
	ll wall=1<<n;
	rep(i,0,wall-1)
	 pd[i]=((!(i&(i<<1)))&&(!(i&(i>>1)))); //判断该状态在此行是否违法
	f[0][0]=1;
	rep(i,1,m) //枚举行数
	 rep(j,0,wall-1) //枚举状态
	  if(pd[j]&&((j&val[i])==j)) //j合法,不会出现在贫瘠的地上种草
	   rep(k,0,wall-1)
	    if(!(k&j)) //k 状态合法 
	     f[i][j]=(f[i][j]+f[i-1][k])%p; 
	ll ans=0;
	rep(i,0,wall-1) ans=(ans+f[m][i])%p;
	printf("%lld",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值