状压入门(易懂之看不懂来打我)

状态压缩,是一种利用二进制的暴力枚举法.

介绍一下几个二进制运算符,以后都经常用

与&  1&0 = 0  1&1 = 1  0&0 = 0  

或|  1|0  = 1  1|1=1  0|0=0

异或^  1^0=1  0^0 =0  1^1 = 0

取反~  ~1=0  ~0=1

& 是两边都是1结果才为1

| 只要有一个是1就是1

^ 只有两边不相等的时候才是1

~ 0变1,1变0

到这里还很简单吧?   

然后,现在有一个数为7,二进制位0111

    还有一个数11,二进制1011

问你7|11为---------------------------------------------------当然是1111也就是15了!!

看起来你对这几个位运算比较熟悉了,emmmm,那我再介绍几个公式(重点,敲黑板) 

以下操作都是对数k而言   解释
判断k得第i位是不是1 k&(1<<(i- 1))                       1<<(i-1)构造了一个只在第i位为1的数,进行&操作,只有当第i位都是1结果才为1,那么可以利用结果判断.
将两个二进制的1合并 k|b 这个简单,合并1直接用|
将k的第i位置为1 k|=(1<<(i-1))        因为是|,所以K的1全部保留,而且因为(1<<(i-1))的第i位有1
将k的第i位置为0 k&=~(1<<(i-1))                               和上面差不多,不过因为取反,所以k与了一个第i位为0其他位都是1 的数

相信你有点迷糊了,不过别担心,如果看不懂直接往下看例题,我会解释的。


第一个例题:方格取数

题目大意:给你一个n*n的格子的棋盘,每个格子里面有一个非负数。
从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取的数所在的2个格子不能相邻,并且取出的数的和最大。

思路:既然在说状压,那我就开门见山了。我们可不可以用二进制来快捷得表示某一行取数情况呢?

当然可以!!举个例子,当n等于5时

拿7来说,二进制为00111,说明取了这一行得第3,4,5个数

那么当n=5时,用多少个数可以表示所有状态呢?答案是2^5

我们进一步观察发现,7是不合法得状态,因为数字不能连续取出,也就是二进制中得1不能连续出现

所以,我们先用一个for循环,求出在2^5个状态中合法的状态

for(int i=0;i<(1<<5);i++)
{
    if(i & (i<<1) )    continue;//不合法
    if(i & (i>>1) )    continue;//不合法
    vector[cnt++]=i;//储存状态 
}

那么你肯定要问,为什么i&(i<<1)是不合法的状态的

那我们的老朋友7来说,00111,左移一位也就是01110

相与的结果就是00110,结果不是0,所以不合法。这个仔细想想,很好理解。

那么我们进入第二步,设计状态

定义dp[i][j]为枚举到第i行状态为j的最大收益

那么转移方程dp[ i ] [ j ] =max(dp [ i ] [ j ] ,dp [ i-1 ] [ k ]+当前行状态为j的收益)

其中状态vector[j]不能和vector[k]上下相邻,也就是相同位上不能同时为1

for(int i=1;i<=n;i++)//枚举行数
{
    for(int j=1;j<cnt;j++)//枚举事先储存在vector中的状态
    {
        int val=ji(i,vector[j]);//计算第i行状态vector[j]的收益 
        for(int k=1;k<cnt;k++)    
        {
            if( (vector[j]&vector[k]) )    continue;//不能上下相邻 
            dp[i][j]=max(dp[i][j],dp[i-1][k]+val); 
        }
    }
}

转移之类的都好说,我们来到了最后一个问题,如何求出第i行状态为vector[j]的收益呢?

比如对于状态vector[j],我们逐位判断是不是1,是就加上。然后把vector[j]右移一位,继续判断 

int ji(int i,int k)//第i行状态为k
{
    int ans=0,cnt=n;
    while(k){
        if(k&1)    ans+=a[i][cnt];//最后一位是1,加上
        k>>=1;cnt--; 
    }
    return ans;
} 

那么看到这里,恭喜你初步掌握了这种状态间的压缩。好好再体会一下下面完整的代码

#include <iostream>
using namespace std;
#define max(a,b)  (((a)>(b))?(a):(b))
int a[19][19],dp[19][1<<17],vector[1<<17],n,cnt=1;
int ji(int i,int k)//第i行状态为k
{
    int ans=0,cnt=n;
    while(k){
        if(k&1)    ans+=a[i][cnt];//最后一位是1,加上
        k>>=1;cnt--; 
    }
    return ans;
} 
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++)    cin>>a[i][j];
    for(int i=0;i<(1<<n);i++)
    {
        if(i & (i<<1) )    continue;//不合法
        if(i & (i>>1) )    continue;//不合法
        vector[cnt++]=i;//储存状态 
    }
    int maxn=0;
    for(int i=1;i<=n;i++)//枚举行数
    {
        for(int j=1;j<cnt;j++)//枚举事先储存在vector中的状态
        {
            int val=ji(i,vector[j]);//计算第i行状态vector[j]的收益 
            for(int k=1;k<cnt;k++)    
            {
                if( (vector[j]&vector[k]) )    continue;//不能上下相邻 
                dp[i][j]=max(dp[i][j],dp[i-1][k]+val); 
                maxn=max(maxn,dp[i][j]);
            }
        }
    }
    cout<<maxn;
    return 0;
}
View Code

这里还有一些状压的入门题,拿去不谢哦(●'◡'●)

蓝桥杯分糖果:http://oj.ecustacm.cn/problem.php?id=1460

玉米田:https://www.luogu.com.cn/problem/P1879

炮兵布阵:https://www.luogu.com.cn/problem/P2704

原文地址:https://www.cnblogs.com/iss-ue/p/12458651.html