POJ 3254 Corn Fields
题意:在一个n*m的草地上面放牛,两头牛不能挨着,并且只能最肥沃的草地上放牛。问有多少中放养方式。
思路:状压dp主要是运用的的位运算,然后用dp的思想去找转移方程。
对于本题而言可以这样考虑,把每一排的所有不相邻的情况看成是一个集合(看成是save[]的话),对于样例可以得到下面的集合
注:这是对于m==3,所有的情况的集合
每一次在这个集合中选取符合条件的(条件就是只能在肥沃的土地上面放牛),换句话说就是,用一个集合(状态压缩)维护所有不相邻的情况,在这个的基础上面再去考虑那些土地能放牛那些不能。
这是每一排的处理。
对于排与排之间,可以这样想, 第i排的所有情况=第i排本身的所有排列+第i的所有不与第i-1排相邻的所有情况(dp思想)。 用数组dp[i][k]表示第i排在情况k的的条件下的放牛的方式。(情况k指的的原来的已经维护好的数组save[k]的情况下。 一开始在这上面我晕了好长时间,后来看了几遍代码自己感受了一下才算是知道。)
转移方程可写成 dp[i][k]=(dp[i][k]+dp[i-1][p]),
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
#define inf 0x3f3f3f
#define MOD 1000000000
int n,m;
int cur[20];
int dp[20][600];
int save[600];
int num=0;
bool rec(int x) //判断是否有相邻
{
if(x&(x<<1)) return false; //
return true;
}
void inint() // 把所有的情况都处理出来,然后放到集合里面去;
{
num=0;
int top=1<<m;
for(int i=0; i<top; i++){
if(rec(i)) save[num++]=i;
}
}
bool check(int x,int k) //检查该集合的基础上,有没有符合条件放牛的;
{
if(x&cur[k]) return false;
return true;
}
/* ps:原作者的直接拿过来了
此处,注意要用相反存储的数据来判断,
因为若10101001是一种可行状态,则可知101001也可行(是前者的一部分)
这时x即为10101001,cur[k]为10110,x&cur[k]=0,即符合条件
*/
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(dp,0,sizeof(dp));
memset(cur,0,sizeof(cur));
memset(save,0,sizeof(save));
inint();
for(int i=0; i<n; i++){
int x;
cur[i]=0;
for(int j=0; j<m; j++){
scanf("%d",&x);
if(x==0) cur[i]+=1<<(m-j-1); //反向表示土地的肥沃和贫瘠
}
}
for(int j=0; j<num; j++){ //先处理完第一排
if(check(save[j],0)) dp[0][j]=1;
}
for(int i=1; i<n; i++){
for(int j=0; j<num; j++){
if(!check(save[j],i)) continue;
for(int k=0; k<num; k++){
if(!check(save[k],i-1)) continue; //j代表的是当前排,k代表的是i-1排,为了检查相邻两排的非法情况
if(save[j]&save[k]) continue;
dp[i][j]=(dp[i][j]+dp[i-1][k])%MOD;
}
}
}
int ans=0;
for(int i=0; i<num; i++){
ans=(ans+dp[n-1][i])%MOD;
}
printf("%d\n",ans);
}
return 0;
}