poj 3254(状压dp入门)

题目链接: http://poj.org/problem?id=3254

状压dp一般范围都较小,这类dp一般数据范围有一项很小(好像是不超过16吧),看到这种数据范围就可以往状压上想

先提一下位运算,       '&'   表示对两个数的二进制进行操作, 相同位如果都为1, 则此位最终结果为1。

                                y >> x , 表示将数 y的二进制向右移 x 位, 低位舍弃,高位补 0

                                 <<     运算符同理

题意:农夫FJ有一块n行m列的矩形土地, 有的土地是肥沃的,可以在这些土地上放牛(用1表示),有的土地不能放牛(用0表示),而且相邻的可以放牛的格子不能同时有牛,问FJ一共有多少种放牛的方案(一头牛都不放也是一种方案)。

分析:以样例为例,我们可以一行一行的考虑。假如对于每一行,用1表示放牛,0表示不放牛,

第一行的状态为:000001010011(舍弃) 100101110(舍弃)111(舍弃)

符合题意的状态只有5个,所以第一行有5种方案。

第二行的状态为:000010

但是第二行中的010和第一行中的010冲突,所以如果第二行状态为010时,共有4种方案;状态为000时,有5种方案,所以一共有4+5=9种方案。

分析完我们会发现,对于每一行,都可以一个01串来表示这一行的状态,而这个状态可以用一个十进制整数来代替,也就是说,把这个状态压缩成了一个十进制整数,所以称为是状态压缩。

本题中,dp[i][j] 就表示第i行状态为j时的方案数。

状态压缩dp中最常见的就是位运算,因为位运算可以很方便的判断当前状态是否合法。例如本题中判断第i行是不是有两块相邻的土地同时都有牛,假设当前状态为X,那么只需要判断X&(X>>1)或者X&(X<<1)的结果是不是0,如果是0,说明没有相邻的,否则就说明有相邻的。

AC代码:

#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;

const int mod = 100000000;
const int maxn = 1 << 12 + 4;    // + 太多会 MLE
int dp[14][maxn];   //到第i行,j状态满足条件的个数
vector<int>v[14];   //存放第i行的状态
int mp[14][14];      //存放地图
int n , m;

int pow2(int x){    //通过2的x次方,求状态(也就是转化为10进制用)
    int s = 1;
    for(int i = 0;i < x;i ++)
        s *= 2;
    return s;
}

//根据第x行 mp[x][j] 的值, 将其二进制取反(即0为1,1为0) 算得十进制的数,即可以排除在不能种植的地方种植的方案
int state(int x){    
    int s = 0;
    for(int i = 1;i <= m;i ++)
        s += (!mp[x][i]) * pow2(m-i);  ///即 s += (mp[x][i] == 0) * pow2(m-i);
    return s;
}
int main()
{
    while(~scanf("%d%d",&n,&m)){
        memset(dp,0,sizeof(dp));
        memset(v,0,sizeof(v));
        memset(mp,0,sizeof(mp));
        for(int i = 1;i <= n;i ++){
            for(int j = 1;j <= m;j ++)
                scanf("%d",&mp[i][j]);
        }
        v[0].push_back(0);                          //第一行不会出现与上一行相邻的情况
        int k = 1 << m;                             //如果列数为m, 则其出现二进制最大的情况为  2的m次方 - 1 ,即 k - 1;
        for(int i = 0;i < k;i ++)
            dp[0][i] = 1;                           //第一行除了所有左右相邻的情况后,其他情况都满足
        for(int i = 1;i <= n;i ++){
            int tmp = state(i);
            for(int j = 0;j < k;j ++){
                if(j & (j << 1)) continue;         //排除j的二进制中含两个相邻的1
                if(j & tmp) continue;              //通过tmp 求的 mp在此行的 几种子情况(土地为1的点,可种植,可不种植)
                v[i].push_back(j);                 //将满足条件的状态放入v[i]
            }
            for(int j = 0;j < v[i].size();j ++){
                int now = v[i][j];                   //从v[i]中提取当前此行的所有状态
                for(int f = 0;f < v[i-1].size();f ++){
                    int last = v[i-1][f];            //从v[i-1]中提取上一行的所有状态
                    if(now & last) continue;        //冲突(即上下相邻)
                    dp[i][now] = (dp[i][now] + dp[i-1][last]) % mod;
                }
            }
        }
        int ans = 0;
        for(int i = 0;i < k;i ++)
            ans = (ans + dp[n][i]) % mod;
        printf("%d\n",ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/no_o_ac/article/details/81170058
今日推荐