状态压缩DP入门

158 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

 状态压缩DP

    大体谈一下为什么叫做状态压缩DP,是因为有些问题的一个子问题就有有很多的状态,如果直接保存按照一般的线性DP来做,数组往往开的太大了!所以呢,咱们就可以把一个状态压缩成一个十进制的数,通俗的举个栗子:
如果说:有5个门,1代表开,0代表关。那么:0 1 1 0 1 就代表 关 开 开 关 开  可以用一个十进制的数字13来代替,这样就把一个状态为  (关 开 开 关 开)压缩成 --》 13.

状态压缩DP基础知识讲解

    如果想学会状压DP,必需要掌握一些基本的位运算的知识,因为压缩状态往往需要通过二进制(根据题目要求用不同的进制压缩状态,大多是二进制就可以解决,根据题目要求灵活运用)的转换,来压缩。

    首先介绍一下运算符号:
1、 & 与   -->  a & b  只有 a b 都等于 1 ,a & b == 1
栗子:            10101
&   1001    
== 00001 
2、| 或     --> a | b 只要 a 与 b 有一个是1 ,a | b == 1
荔枝:          10101
|     1001    
==  11101
3、^ 异或 --> a ^ b 只要 a b 不相同 , a ^ b == 1
励志:          10101
^    1001    
==  11100
4、>> 向后进位    5 的二进制为 :101  ,5>>1 == 010  >>的作用就是  把   一个数对应的二进制的最后一个二进制位抹去,或者也可以理解为每个二进制位都向右平移一位,而最右边的一位数被挤了出来,挤没了。(其实>>1 相当于除以 2 , >>2 相当于除以4,用位运算会比 “ / ” 直接除更快呦!)
5、<< 向前进位    5 的二进制为 :101  ,5<<1 == 1010  <<的作用就是  在   一个数对应的二进制的最后一个二进制位后面加一个0,或者也可以理解为每个二进制位都向左平移一位,而最右边的一位数被空出来了,用0补上。(其实<<1 相当于乘以 2 , <<2 相当于乘以4,用位运算会比 “ * ” 直接除更快呦!)

例题:

    学会了状态压缩需要的基础知识后,咱们通过一个简单的小例题说明怎么来用状态压缩DP算法。
先附上题目的链接,学会后,自己动手尝试AC呦:3254 -- Corn Fields

 我来给大家描述一下吧,毕竟看中文还是快(hiahiahia~~~)
题目描述:给你M * N 块的土地,让你在每一小块地上种
。。。。。。现在很无语。。
昨天晚上写了将近2小时快写完了,然后点了发布博客,在题目描述:给你M * N 块的土地,让你在每一小块地上种这句话后面的博客全部消失了!!! 烦了一个小时。 然后!今天又认认真真写了一个晚上!终于写完了,心里暗喜觉得只要会c++的都能看懂吧~ 然后点了保存修改,发布博客,然后!! 又出现了昨天晚上的情况。。 这回是真崩溃了。。。 博主已哭晕n次,心里不甘!!! 于是找原因,发现当粗 我想给大家更好的视觉效果,于是用了​编辑
这个玉米图标,所以种后面的所有的东西都消失了,在这提醒一下大家,以后写博客最好不要用输入法里的图==(不作不会死。。。)
之前给同学讲过2遍这个例题,我又写了两边这个题解加DP入门的思想与讲解还有总结,,,现在都没了。。
最进一段时间是没有心情写了,想到这个题都恶心了,于是在这吐槽一下,最后只能勉强附上这个题的AC代码了,。。。。

//http://poj.org/problem?id=3254
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int M, N, l;
long long land[30];
long long state[30];
long long dp[30][10000];
int main()
{
    scanf("%d %d", &M, &N); //输入
    for(int i = 1; i <= M; i++){
        for(int j = 1; j <= N; j++){
            scanf("%d", &l);
            if(l == 1) land[i] += (1 << (N - j));
//   通过位运算计算该行的状态对应的数字,大家可以自己举个栗子试试就明白了,
//   为什么这么计算,其实就相当于给你一串 为二进制数,
//   让你转换成十进制,大家也可以用自己的方法来转换。
        }
    }


    int len = 1; //记者点有多少个符合调节的状态
    for(int i = 0; i < (1 << N); i++){  //把所有的可能的状态遍历一遍
        if((i & (i << 1)) == 0){  //判断是否符合咱们的要求(不可以相邻)
            state[len++] = i;  //存下来,这样把一个状态压缩成一个数后就好存多了吧!
        }
    }



    for(int i = 1; i < len; i++){
        if((land[1] & state[i] ) == state[i])
            dp[1][i] = 1;
    }


    for(int i = 2; i <= M; i++){ // 从第 2 行一直到第 M 行
        for(int j = 1; j < len; j++){  // 先看一下第 i 行的所有可以种植的状态

            if((land[i] & state[j]) != state[j]) continue;  // 如果不能种,那么返回,继续找。

            for(int k = 1; k < len; k++){ //当第i行的state[j]状态符合第i行土地的时候
//看一下 i 行的前一行的所有状态,

                if((land[i - 1] & state[k]) != state[k]) continue;
//当第i - 1行的state[k]状态符合第i - 1行土地的时候
                if(state[j] & state[k])                  continue;
//当第i行于第i-1行种植方法不相邻的时候,如果有上下紧挨着的,那么 state[j] & state[k] 肯定大于0,
//大于0就不符合,所以continue。
//当第i行的一个种植方法 符合第i行的土地的时候,而且 第i-1的一个种植方法也符合其i-1行的土地,而且
//第i行于第i-1行也没有相临的元素的时候 让第i行的第j状态的 可以种植的方法数目就等于它自己加上前一行当状态为k的时候的方法数。
                dp[i][j] = (dp[i][j] + dp[i - 1][k]) % 100000000;//按题目要求,得取余
            }
        }
    }

    long long ans = 0;
    for(int j = 1; j < len; j++)
            ans = (ans + dp[M][j]) % 100000000;
    printf("%lld\n" ,ans );
    return 0;
}