第一道状态压缩dp :)
考虑每一行的情况,如果我们令0表示不可以放牧1表示放牧,那么这一行所有可行的情况都可以穷举出来并对应到一个十进制的数;这就是状态压缩。再由题目可以知道每一行的状态可不可以出现只和它前面的那一行有关,所以我们可以定义 dp[i][j]表示第i行处于第j种状态的时候有多少种放牧的方法;
dp[i][j]=dp[i-1][j1]+dp[i-1][j2]+。。。。+dp[i-1][jn],其中j要和jn能同时出现。
由这个方程我们就可以写程序了,我们可以选择用一个数组来表示状态然后进行相应的判断,但是我们可以通过位运算更方便的实现这些判断。
首先我们不需要把穷举每个状态,我们可以通过题目限制条件(两个1不能相邻)先筛选出所有合法的状态,这个操作用x&(x<<1)进行判断,如果为0则表示无相邻否则相反;然后我们还要判断这些合法状态是否和是否和初始状态冲突,这时我们需要将初始状态01取反,0变成1,1变成0(这样只需要判断如果与原状态1位置上对应的状态是不是0,可以通过&实现),这样我们只需要将某个状态与其&,如果为1则表示不合法,为0则合法。最后我们需要判断对应两个数的二进制表示的相同位是否同时为1,这个也可以通过&实现。
这些操作都用位运算实现后,我们就可以方便的写出代码了。
代码如下
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define mod 1000000000
int total[1000];
int dp[15][1000];
int top;
int cur[15];
int n,m;
int ok(int i,int j)
{
if(i&j)
return 0;
return 1;
}
void Allstate()
{
top=1;
int j=1<<m;
for(int i=0;i<j;i++)
{
if(i&(i<<1))
continue;
total[top++]=i;
}
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
Allstate();
memset(cur,0,sizeof(cur));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
int x;
scanf("%d",&x);
cur[i]=cur[i]*2+x;
}
cur[i]=~cur[i];//实现按位取反
}
memset(dp,0,sizeof(dp));
for(int i=1;i<top;i++)
{
if(ok(cur[1],total[i]))
dp[1][i]=1;
}
for(int i=2;i<=n;i++)
for(int j=1;j<top;j++)
{
if(ok(cur[i],total[j]))
{
for(int d=1;d<top;d++)
{
if(ok(total[d],total[j]))
dp[i][j]=(dp[i][j]+dp[i-1][d])%mod;
}
}
}
int ans=0;
for(int i=1;i<top;i++)
{
ans=(ans+dp[n][i])%mod;
}
printf("%d\n",ans);
}
return 0;
}