POJ3254:状态DP入门级理解

状态DP与二进制及位运算密切相关。
在这道题中,对于 一行0或1 所表示的状态,我们就可以把它们当作一个数的二进制,而它们所代表的“数”,即是表示这一行的状态。
比如(111) 就可以用 一个数 7 来表示 这一行的状态
那么接下来就要去判断 我们安排的装态 是否合法,由题意,只能在“1”上放牧,而且任意两只牛相邻是不合法的,
那么要考虑的主要有三个方面:
1: 一行安排的状态,要与这一行实际状态相容,即只能在“1”上放牧;
设该行实际状态为 i ,我们安排的状态为 j,
则合法状态满足 j & ( ~i ) == 0
理解:
将 i 取反再与 j 相与,结果为零,
如果 j 在某一位上为0,显然是合法的;如果为1,则 ~i 对应位置上必为0才能使最终结果为零;
而此时i 实际对应位== 1; 可以放牧,与 j 不冲突。。。(位运算好晕。。。)

2: 安排的状态本身左右相斥,即不能同时为1;
对于安排的状态j, 满足 j&(j<<1)==0 即可,比较简单,自己想想

3.安排的状态上下相斥;(而且当前行状态仅与上一行相关,而与其他行无关,即无后效性)
对于上一行的状态 k, 本行的状态 j,(假设它们已与实际状态相容)
满足 j & k == 0 即可;(自己推)

状态数目 的最大值为 1<<2^(M+1); 但实际上 满足条件2 的的状体就很少了,(cnt==614);
可以将它们用一个state[]数组先记录下来,循环时遍历这些合法状态即可。。。
当然,每一行的实际状态也要用map_r_state[]数组记录下来 (数组名字自己取。。。。)

dp[i][j] 表示第i行状态为state[j]时 的方案数 最终结果将dp[M][j], j={0,1,,,cnt-1};遍历相加即可

贴代码:

#include<iostream>
#include<cstring>
using namespace std;

const int MOD = 100000000;
const int MAXN = (1<<13)+5;


int state[MAXN];      //实际合法状态会很少

//当前状态是否合法(左右相容)
//记录合法状态
int cnt = 0;
void state_is_legal()
{

    for(int j=0; j<MAXN; ++j)
     if( !(j&(j<<1)) ) //j&(j<<1))==0
         state[cnt++] = j;     //只要记录可行状态就好了
}

//是否符合实际情况
bool r_and_v_is_legal(int n, int j)
{
    if( !(j&(~n)) ) return true;
      else return false;
}

//上下两行状态是否相容
bool l_and_n_is_legal(int j, int k)
{
    if( !(j&k) ) return true;
      return false;

}



int main()
{

     memset(state,0,sizeof(state));
     state_is_legal();
    //cout<<"cnt    "<<cnt<<endl;
     int M,N;
     while(cin>>M>>N)
     {
         int map[M+1][N+1];
         int map_r_state[M+1];
         memset(map_r_state,0,sizeof(map_r_state));

         for(int i=1; i<=M; ++i)
           for(int j=1; j<=N; ++j)
            {
                cin>>map[i][j];
                if(map[i][j]) map_r_state[i] += (1<<(N-j));  //注意下标,这里是从1开始
            }


         int dp[M+1][MAXN];
         memset(dp,0,sizeof(dp));

         //初始化第一行
         for(int j=0; j<cnt; ++j)
            if(r_and_v_is_legal(map_r_state[1],state[j]))
                dp[1][j] = 1;


         for(int i=2; i<=M; ++i)
            for(int j=0; j<cnt; ++j)
            {
              if(r_and_v_is_legal(map_r_state[i],state[j]))
              {
                  for(int k=0; k<cnt; ++k)
                    if(r_and_v_is_legal(map_r_state[i-1],state[k]))
                    {
                        if(l_and_n_is_legal(state[j],state[k]))
                            dp[i][j] += dp[i-1][k];    //直接用j表示第j个合法状态
                    }
              }

            }

            int ans = 0;
            for(int j=0; j<cnt; ++j)
              ans = (ans+dp[M][j])%MOD;
              cout<<ans<<endl;
     }

return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值