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


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

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

    与&  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

  • 相关阅读:
    用来验证字符串的规则引擎
    C++基本错误:
    web service传递stream二进制对象的解决方法
    安装EnterpriseLibrary4.1后遇到PresentationBuildTasks找不到的问题
    关于NHibernate示例程序QuickStart无法运行的问题
    关于iframe.document
    .NET下转换日期格式为中文大写
    History(历史)命令用法 15 例
    防火墙技术发展趋势浅析
    MIME类型大全
  • 原文地址:https://www.cnblogs.com/iss-ue/p/12458651.html
Copyright © 2020-2023  润新知