zoukankan      html  css  js  c++  java
  • 状压dp

    状态压缩DP

    [TOC]

    1.状态压缩的定义

    状态压缩的定义:我们知道任何一个二进制都可以对应唯一的十进制数,反过来也成立。所以我们可以用一个数来代替一组数从而降低维数。这种解题手段我们叫做状态压缩。

    举个例子:如果数组中的某一行全是0或全是1,例如000,001,我们可以将000用0表示,001用1来表示,这样一来就降低了维数,起到了状态压缩的作用。状态压缩dp就是基于此的dp。

    2.位运算

    状压dp与位运算关系密切,所以先了解一下位运算。

    基本的一些位运算(如&,|,^,~,<<,>>)的基本用法就不多做介绍。来说一下它们的一些运用:

    1:判断一个数x的二进制下第 i 位是不是1:(x & 1 << i)

    2:将一个数x的二进制下第 i 位改为1:(x = x | (1 << i - 1))

    3:把一个数二进制下最靠右的第一个1去掉:(x &= x - 1)

    ......待补充

    3.入门例题

    1:poj-3254

    题意:一个n*m的草地,1代表有草,0有没。两个草不能相邻,问有多少种选法。

    状态:dp[i][state[j]]表示前 i 行,第 i 行采用第 j 个状态时得到的总方案数。所以可得状态转移方程:dp[i][state(j)]=dp[i-1][state(k1)]+dp[i-1][state(k2)]+......+dp[i-1][state(kn)]

    show code:

    #include<bits/stdc++.h>
    
    using namespace std;
    typedef long long ll;
    const int mod=1e8;
    const int maxn=1e5+10;
    int state[maxn],dp[15][maxn];
    int arr[15][15],n,m,tot;
    void init()                     //求出合理状态打个表
    {
        int maxlen=1<<12;
        for(int i=0;i<maxlen;++i){
            if((i&(i<<1))==0)    state[++tot]=i;    //与运算要打括号!!!
        }
    }
    bool check(int row,int x)
    {
        for(int i=1;i<=m;++i)
        {
            if(x&(1<<(i-1))&&!arr[row][i])      //检查一下x的所有位是否与已求出的合法状态冲突
                return false;
        }
        return true;
    }
    
    int main()
    {
        init();
        while(scanf("%d%d",&n,&m)!=EOF)
        {
            for(int i=1;i<=n;++i)
                for(int j=1;j<=m;++j)
                    scanf("%d",&arr[i][j]);
            memset(dp,0,sizeof(dp));
            for(int i=1;i<=n;++i)
            {
                for(int j=1;state[j]<(1<<m);++j)
                {
                    if(!check(i,state[j]))  continue;      //保证下面的情况都与输入不冲突
                    if(i==1){
                        dp[i][j]=1;
                        continue;
                    }
                    for(int k=1;state[k]<(1<<m);++k){     //根据状态转移方程而来
                        if((state[k]&state[j])==0)        //同一列没有两个1,两个数做与运算一定要打括号!!!!
                            dp[i][j]+=dp[i-1][k];
                    }
                }
            }
            /*for(int i=1;i<=n;++i){
                for(int j=1;state[j]<(1<<m);++j)
                    printf("%d ",dp[i][j]);
                printf("
    ");
            }*/
            ll res=0;
            for(int i=1;state[i]<(1<<m);++i)
                res=(res+dp[n][i])%mod;
            printf("%d
    ",res);
        }
        return 0;
    }
    

    2:poj-3311

    题意:一个有向图,一个点可以走多次,问从0号点开始走把所有点都走一遍且最后回到原点的最短路径是多少。

    #include<bits/stdc++.h>
    
    using namespace std;
    const int inf=0x3f3f3f3f;
    const int maxn=15;
    int arr[maxn][maxn];
    int n,dp[1<<11][maxn];
    
    int main()
    {
        while(scanf("%d",&n)&&n)
        {
            for(int i=0;i<=n;++i)
                for(int j=0;j<=n;++j)
                    scanf("%d",&arr[i][j]);
            for(int k=0;k<=n;++k)                 //预处理求出任意两点之间的最小距离
                for(int i=0;i<=n;++i)
                    for(int j=0;j<=n;++j)
                        if(arr[i][k]+arr[k][j]<arr[i][j])
                            arr[i][j]=arr[i][k]+arr[k][j];
            for(int s=0;s<(1<<n);++s)       //s表示状态
            {
                for(int i=1;i<=n;++i)                       //状态要从1开始表示,但其实是第i+1号节点(从1开始编号的话)
                {
                    if(s&(1<<(i-1)))
                    {
                        if(s==(1<<(i-1)))           dp[s][i]=arr[0][i]; //如果刚好只到城市i,也是边界情况
                        else{
                            dp[s][i]=inf;
                            for(int j=1;j<=n;++j)                       //找一个合适的点使转过去之后距离最小
                                if ((s>>(j-1))&1 && j!=i )
                                    dp[s][i]=min(dp[s][i],dp[s^(1<<(i-1))][j]+arr[j][i]);
                                    //在s状态没有经过城市i中找一点,使得距离更短
                        }
                    }
                }
            }
            int res=dp[(1<<n)-1][1]+arr[1][0];
            for(int i=2;i<=n;++i)
                res=min(res,dp[(1<<n)-1][i]+arr[i][0]);
            printf("%d
    ",res);
        } s
    }
    

    3:poj 1185

    题意:m*n的一块地, P表示平原, H表示山地, P上可以部署炮车, 炮车的攻击范围为前后左右范围内2个单位的十字行, 不能斜的攻击, 问最多能放多少个炮车。n : 100, m : 10;

    因为每行只有10列, 故可用一个十进制数将所有状态都表示出来, 注意约束条件:①:山地不能放炮车, ②:炮车之间不能相互攻击;故可设$$f[i][j][k]$$表示第 i 行的状态为 j, 上一行的状态为 k 的可部署的最多炮车的数量。直接开100行数组会爆内存,加个滚动数组优化就行了

    #include <bits/stdc++.h>
    #define debug(x) cout << #x << " = " << x << endl; 
    
    using namespace std;
    typedef long long LL;
    const int maxn = 1 << 11;
    const int inf = 0x3f3f3f3f;
    const double eps = 1e-6, pi = acos(-1.0);
    int gcd(int a,int b)    { return b == 0 ? a : gcd(b, a % b); }
    
    int n, m;
    int f[2][maxn][maxn], g[110];
    char s[110][11];
    vector<int> state;
    
    bool ok(int x){
        for(int i = 0; 1 << i <= x; ++i){
            if((x & 1 << i) && ((x & 1 << i + 1) || (x & 1 << i + 2))) return 0;
        }
        return 1;
    }
    
    int main()
    {
        scanf("%d %d", &n, &m);
        for(int i = 0; i < n; ++i){
            scanf("%s", s[i]);
            for(int j = 0; j < m; ++j){
                if(s[i][j] == 'P')  continue;
                g[i] += 1 << j;
            }
        }
        for(int i = 0; i < 1 << m; ++i){    //打表, 将符合条件的状态存起来
            if(ok(i))   state.push_back(i);
        }
        int len = state.size();
        for(int i = 0; i <= n + 1; ++i){    //阶段: 枚举到哪一行了
            for(int j = 0; j < len; ++j){    //状态:上山一行的状态
                for(int k = 0; k < len; ++k){    //状态:上一行的状态
                   for(int u = 0; u < len; ++u){    //状态:这一行的状态, 顺序可以乱
                        int now = state[u], pre = state[k], ppre = state[j];
                        if((now & pre) || (now & ppre) || (pre & ppre)) continue;
                        if(g[i] & now) continue;
                        f[i&1][now][pre] = max(f[i&1][now][pre], f[i-1&1][pre][ppre] + __builtin_popcount(now));
                        //决策, 这个状态可以从上一行的状态转移而来
                    }
                }
            }
        }
        printf("%d
    ", f[n+1&1][0][0]);
    }
    
  • 相关阅读:
    [SAP HANA] HANA 安装更新工具HDBLCM
    [SAP BASIS] [TMS] TMS相关的程序和后台作业
    [sap basis] [authorization trace] Disable/enable authorization trace
    IEEE 802.11 标准列表
    802.11 wireless 七
    802.11 wireless 六
    802.11 wireless 五
    802.11 wireless 四
    802.11 wireless 三
    802.11 wireless 二
  • 原文地址:https://www.cnblogs.com/StungYep/p/12254167.html
Copyright © 2011-2022 走看看