题目链接:http://poj.org/problem?id=3254
题目意思:给你一个n*m的牧场,叫你带牛去吃草,其中0代表没有草不可以放牧,1代表有草可以放牧。而且两头牛不可以相邻,叫你求所有可能的放牧方案。
思路:这是个状态压缩的基础题,刚学状态压缩的可以用这个题目来理解状态压缩;(如果是刚学DP我建议理解题意后先粗略的看一下代码后再边看代码边看我的思路,效果更佳)
1、题目告诉我们两头牛不能相邻,那么我们就可以枚举出每一行的所有情况,并且用数组st保存起来;怎么枚举每一种情况呢,题目说明不放也是一种方案,所以我们从0开始放,这里就可以很巧妙的用二进制来枚举,对于每一个位置,我们都可以选择放或者不放,用0表示不放,用1表示放。所以共有2^m(m为每一行的列数,用1<<m表示)中情况,并且枚举完情况之后,我们用judge1来判断是否存在相邻1的情况,剔除之,保存不存在相邻为1的情况;其中用位运算可以直接判断是否含有相邻的1;(x<<1&x)返回为1说明存在相邻的1,0则反之;位运算思想我相信接触多了就会很自然很习惯的使用;
2、题目还告诉我们只有在有草的地方才能够放牧,这就又给我们多了一个限制条件,这样可以给我们剔除很多方案,降低数据的庞大;那么我们也先将题目给的土地状态用一个数组Map保存起来。这里我们同样用二进制转换成十进制保存,但是这里我们需要注意的就是我们保存土地的相反数,比如说:101(1为可以放牧的),我们保存成010,为什么要这样保存呢??这是为了方便后面判断是否会和该行的状态相矛盾;即假设我当前的状态是010,判断这种状态是否会和土地状态相冲突,我们之前保存的就可以直接与土地状态&运算一下,如果返回的是1说明就相冲突了,因为我们Map数组的1是保存的不能放的状态;当土地不能放牧的时候用1<<(j-1)移位来保存该行的土地状态;
3、明白了这两点我想这个题目就很清楚了;我们先把第一行的情况都处理一下;然后从第二行开始枚举每一行事先枚举保存的的各种符合情况的状态st[i];判断是否与土地状态相矛盾;如果不矛盾,我们就判定是否与上一行的各种状态相矛盾,如果不矛盾,那么该行的这种状态的方案数就加上上一行符合情况的状态的方案数,这就是状态转移方程;dp[i][j]+=dp[i-1][f],(0<=f<k);
4、最后统计第n行各种状态的方案数总和就可以了;(当然这道题必要的基础就是对位运算要有较好的理解);
关于位运算,大家可以简单的看一下百度百科:http://baike.baidu.com/link?url=TnwY0uf6_qCLW1M9GFys6L-lgzMf4kmE23UsI7Uw67mHl_HFrY8eVwgnutKnOwRcHK-xRXf9dpI58UYnvqxp5a
#include<iostream> #include<string> #include<cstdio> #include<cstring> #include<queue> #include<map> #include<cmath> #include<stack> #include<set> #include<vector> #include<algorithm> #define LL long long #define inf 1<<30 using namespace std; const int N=13; const int M=1<<N; const int mod=100000000; int n,m,x; int st[M]; // 存每一行的状态; int Map[M]; // 存取反后给出的地的状态; int dp[N][M]; // 用于存第i行状态为j的时候可以放牛的种数; bool judge1(int x) // 判断左右是否存在相邻的1; { return (x&(x<<1)); // 如果不为0那就说明有相邻的1; } bool judge2(int i,int x) // 判断该行的状态是否与土地的状态相矛盾; { return (Map[i]&st[x]); } int main() { while(~scanf("%d%d",&n,&m)){ memset(dp,0,sizeof(dp)); memset(st,0,sizeof(st)); memset(Map,0,sizeof(Map)); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ scanf("%d",&x); if(x==0){ // 不合法状态; Map[i]+=(1<<(j-1)); // 通过位移存入Map[i],表示该行不合法状态; } } } int k=0; for(int i=0;i<(1<<m);i++){ if(!judge1(i)) st[k++]=i; // 保存不存在相邻为1的状态数; } for(int i=0;i<k;i++){ // 初始化第一行各种状态下的总数; if(!(judge2(1,i))) dp[1][i]=1; } for(int i=2;i<=n;i++){ // 枚举每行k种符合不含相邻1的情况; for(int j=0;j<k;j++){ if(judge2(i,j)) continue; // 剔除不符合情况与土地状态矛盾的情况; for(int f=0;f<k;f++){ if(judge2(i-1,f)) continue; // 剪枝,去除上一行不符合情况; if(!(st[j]&st[f])){ // j状态是否与上一行矛盾; dp[i][j]+=dp[i-1][f]; // 意思就是这一行放这种状态的种数等于上一行可以放的各种状态的种数之和; } } } } int ans=0; for(int i=0;i<k;i++){ // 所以很显然左后一行的各种状态之和就是所有合法状态的方案数; ans+=dp[n][i]; ans%=mod; } printf("%d\n",ans); } return 0; }