蒟蒻的状压入门

状态压缩这个东西,本质上是利用进制之间的转换降低表示状态的维数。一般用二进制的一串数字来表示当前的所有状态,再将其转为十进制,可以大大减少。

例如

有二进制数 100011011(九位),每一位表示该农田是否被占用,1表示用了,0表示没用,这样一种状态就被我们表示出来了:见下表

设n = 9;
 

列数123456789
二进制00011011

 

我们最多只需要2^{^{n+1}}-1个十进制数就可以把所有情况全都表示出来。


在明确了这一点后,我们来看一下状态间的变化

在这里引用这篇博文原文地址,做了一些高亮标注。

一个有n个不同元素的集合中,去表示我当前已经取得的元素状态; 

比如如果 n = 3的话 , 0(000)表示的是我手中什么都没有, 1(001)表示的是我当前取得了第一个元素, 2(010)表示的是我取得了第二个元素, 3(011)表示我当前已经有了第一个和第二个……。

总结: 先将各n个不同的初始化状态离散化标记,然后将我当前状态用二进制表示,如果i个状态我已经具有了, 那么这一位就是1,否则就是0,然后再转化成10进制表示。

那么如何进行状态的添加和减少呢?这就要用到位运算来解决了。

1、“<<”运算符: 1<<n 表示2 的n次方。如果一共有n种初始化状态(也就是集合中有n个元素),那么当下的任意一种状态都可以用一个小于(1<<n)的非负整数表示, 且(1<< (i-1)) 的值就是单独具有第i个状态的 十进制表示。

2、“&” 运算符: 可以表示两个集合的交集,也可用来快速确定当前状态中是否存在第i个状态。

比如 n = 4;

我当前的状态为11(1011)也就是我有了1,2,4这三个初始状态, 另外有一种状态是7(0111)有了1,2,3这三个初始状态, 那么两个集合的交集应该 是(0011)也就是3. 所以 7&11 == 3;

再比如: n = 4;

如果我当前状态为 11(1011), 对于11这个数来说如果不将其转化成二进制数的话,很难直观的确定其中是否存在某种状态,但是我们可以用&运 算符 来确定。 (11 & (1<<(i-1))) 如果这个值为0那么当前不存在第i个状态, 否则就存在。

3、"|" 运算符:表示两个集合的并集。可以用于给当前状态加上第i个状态。 

比如: n = 4, 如果当前状态为 11(1011), 另一状态为7(0111), 7|11 =  15(1111), 如果想给7(1011) 加上的三种状态 可以用 7|(1<<(3-1)) = 15(1111). 更通常的写法 x|(1<<(i-1)) 表示如果当前状态为x那么加上第i个状态的值。(如果x已经具有了当前状态那么, x的值不变)。

4、“^”  运算符, x^y 会的如果 x, y 都具有或者都不具有第i个状态,那么的到的值就不会具有这种状态, 反正则具有这种状态。7^11 = (1100)12;

5、 “>>” 运算符: x>>1 表示x/2 也就是去除x中序号最大的那种状态。
 

状态压缩的思想常常与BFS和DP一起连用。

poj3254 Corn Fields(DP)

题目链接:http://poj.org/problem?id=3254
就像开头举的例子一样,可以用状压来表示一行的情况。

设dp[i][state] 表示第i行状态为state的方案数,dp[n][...]所有可行状态的和就是答案。

枚举每一行,每次获得得到所有的可行状态,存入dp数组

以当前行的可行状态更新下一行的状态

交换上下两行

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <bitset>
#include <queue>
using namespace std;
typedef long long ll;
#define mod 100000000
const int maxn = 12;
int tot[2],dp[maxn][1024],state[2][1024],n,m;
int mat[maxn][maxn];

void getstate(int x,int side) {
    tot[side]=0;
    int i,S,no=0,flag;
    for(i=0;i<=m;i++) if(mat[x][i]==0) no+=(1<<i);
    for(S=0;S<(1<<(m+1));S++) {
        flag=0;
        if((S&no)!=0) continue;
        for(i=0;i<=m;i++) {
            if( (S&(1<<i))!=0 && (S&(1<<(i+1)))!=0 ) flag=1;
            if(flag) break;
        }
        if(flag) continue;
        state[side][++tot[side]]=S;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin>>n>>m;
    n-=1;m-=1;
    for(int i=0;i<=n;i++){
        for(int j=0;j<=m;j++){
            cin>>mat[i][j];
        }
    }
    int ans=0,up=0,down=1;
    getstate(0,up);
    for(int j=1;j<=tot[up];j++) dp[0][state[up][j]]=1;

    for(int i=0;i<n;i++) {

        getstate(i+1,down);

        for(int S=1;S<=tot[up];S++){
            for(int T=1;T<=tot[down];T++){
                if((state[up][S]&state[down][T])==0)
                    dp[i+1][state[down][T]]=(dp[i+1][state[down][T]]+dp[i][state[up][S]])%mod;
            }
        }
        swap(up,down);
    }
    for(int s=1;s<=tot[up];s++){
        ans+=dp[n][state[up][s]];
        if(ans>=mod) ans-=mod;
    }
    cout<<ans<<endl;

    return 0;
}

留坑持续更新。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值