poj 3254(状态压缩DP)
题意:一个矩阵里有很多格子,每个格子有两种状态,可以放牧和不可以放牧,可以放牧用1表示,否则用0表示,在这块牧场放牛,要求两个相邻的方格不能同时放牛,即牛与牛不能相邻。问有多少种放牛方案(一头牛都不放也是一种方案)
解析:根据题意,把每一行的状态用二进制的数表示,0代表不在这块放牛,1表示在这一块放牛。首先很容易看到,每一行的状态要符合牧场的硬件条件,即牛必须放在能放牧的方格上。这样就能排除一些状态。另外,牛与牛之间不能相邻,这样就要求每一行中不能存在两个相邻的1,这样也能排除很多状态。然后就是根据上一行的状态转移到当前行的状态的问题了。必须符合不能有两个1在同一列(两只牛也不能竖着相邻)的条件。这样也能去掉一些状态。第i行为某状态state时的方案数为第i-1行的所有符合条件的状态的方案数的总和。
状态表示:dp[state][i]:在状态为state时,到第i行符合条件的可以放牛的方案数
状态转移方程:dp[state][i] =Sigma dp[state'][i-1] (state'为符合条件的所有状态)
DP边界条件:首行放牛的方案数dp[state][1] =1(state符合条件) OR 0 (state不符合条件)
利用位运算可以巧妙的判断某些状态是否符合条件:
if(a&(a<<1)==1),用于判断a中相邻位是否同为1,等式成立表示存在,否则不存在;
if(a&b),用于判断a和b相同位是否同为1,等式成立表示存在,否则不存在。
AC代码如下:
1 #include<stdio.h> 2 #define M 100000000 3 int dp[13][1<<13],state[1<<13],cur[13]; 4 int m,n,top=0; 5 void init() //预处理所有满足条件(相邻位置不能放牛)的状态 6 { 7 int i,sum=1<<n; 8 for(i=0;i<sum;i++) 9 if(i&(i<<1)) 10 continue; 11 else 12 state[top++]=i; 13 } 14 int fit(int a,int b) //判断二进制数相同位置是否同为1 15 { 16 if(a&b) 17 return 0; 18 return 1; 19 } 20 void DP() 21 { 22 int i,j,k; 23 for(i=0;i<top;i++) //初始化第一行 24 if(fit(state[i],cur[1])) 25 dp[1][i]=1; 26 for(i=2;i<=m;i++) 27 for(j=0;j<top;j++) //遍历第i行所有状态 28 { 29 if(!fit(cur[i],state[j])) //若该状态不符合条件 30 continue; 31 else 32 { 33 for(k=0;k<top;k++) //遍历第i-1行所有状态,找出满足条件的 34 { 35 if(!fit(state[k],cur[i-1])) //这一句其实不用也可以 36 continue; 37 if(!fit(state[k],state[j])) //若相邻位置同为1,不符合条件 38 continue; 39 dp[i][j]=(dp[i][j]+dp[i-1][k])%M; //dp[state][i] =Sigma dp[state'][i-1] (state'为符合条件的所有状态) 40 } 41 } 42 } 43 int ans=0; 44 for(i=0;i<top;i++) //求最后一行所有满足条件的状态 45 ans=(ans+dp[m][i])%M; 46 printf("%d ",ans); 47 } 48 int main() 49 { 50 int x,i,j; 51 scanf("%d%d",&m,&n); 52 for(i=1;i<=m;i++) 53 for(j=1;j<=n;j++) 54 { 55 scanf("%d",&x); 56 if(x==0) //如果该位置不能放牛,则将该行变成相应的二进制数,方便判断 57 cur[i]+=1<<(n-j); 58 } 59 init(); 60 DP(); 61 return 0; 62 }