【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[i−1][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;
}