zoukankan      html  css  js  c++  java
  • 动态规划_状态机与状态压缩DP

    状态机DP

    • 与背包dp不同,处在每个位置或时刻,可以有多种状态

    1049. 大盗阿福

    • 对于每个状态,有选择和不选两种状态
    • 对于当前位置,如果选择,根据题目要求,前一个位置必须不能选
    • 如果不选当前位置,可以从前一个状态中转移过来,也可以从前两个状态转移过来

    1057. 股票买卖 IV

    初始化:

    • (dp[i][j][1])初始时均为不合法,设置为负无穷
    • (dp[i][j][0])设置为零

    状态表示:

    • (dp[i][j][0]): 在前(i)天内,最多执行(k)此交易,当前手中无存货时,所得的最大利益
    • (dp[i][j][1]): 在前(i)天内,最多执行(k)此交易,当前手中有存货时,所得的最大利益

    image

    状态计算:

    • (dp[i][j][0])
    1. 前一天手中无货:(dp[i - 1][j][0])
    2. 前一天买入:(dp[i - 1][j][1] - w[i])
    • (dp[i][j][1])
    1. 前一天手中有货:(dp[i - 1][j][1])
    2. 前一天买入:(dp[i - 1][j - 1][0] - w[i])

    结果:求最多交易(k)次,所获得的最大利益

    1058. 股票买卖 V

    状态表示:

    • (dp[i][0]): 在前(i)天内, 手中有存货
    • (dp[i][1]):在前(i)天内,手中无存货的第一天
    • (dp[i][2]):在前(i)天内,手中无存货了好几天(多于一天)

    状态计算:

    • (dp[i][0])
    1. (dp[i - 1][2] - w[i]):手中无存货了好几天,买入
    2. (dp[i][0]): 手中无存货
    • (dp[i][1])
      (dp[i - 1][0] + w[i]):前一天手中有存货,卖掉了

    • (dp[i][2])

    1. (dp[i - 1][1]):前一天是没有存货的第一天
    2. (dp[i - 1][2]):前一天也是没存货好几天了

    状态压缩DP

    1. 基于连通性的DP(棋盘类)

    291. 蒙德里安的梦想

    • 如果横放的长方形摆放好了,那么竖放的长方形的摆放的方案数就确定了,因此只需考虑横放的数量即可

    • (dp[i][j])表示第(i)列,上一列中那些行伸出来的小方格状态数,伸出来记为1,否则值为零,由此产生的二进制数的十进制表示

    • 两个转移条件:

    1. (i) 列和 (i - 1)列同一行不同时捅出来:((j & k) == 0)
    2. 本列伸出来的状态(j)和上列捅出来的状态(k)求或,得到上列是否为偶数空行状态,如果是奇数空行不转移:
    #include <iostream>
    #include <cstring>
    #include <vector>
    
    using namespace std;
    const int N = 12, M = 1 << N;
    int n, m;
    long long dp[N][M];
    bool st[M];
    vector<int> states[M];
    
    int main() {
        while(cin >> n >> m , n || m) {
            for(int i = 0; i < 1 << n; i++) {
                st[i] = true;
                int cnt = 0;
                for(int j = 0; j < n; j++) {
                    if(i >> j & 1) {
                        if(cnt & 1) {
                            st[i] = false;
                            break;
                        }
                    }
                    else cnt++;
                }
                if(cnt & 1) st[i] = false;
            }
            // cout << endl;
            for(int i = 0; i < 1 << n; i++) {
                states[i].clear();
                for(int j = 0; j < 1 << n; j++) {
                    if((i & j) == 0 && st[i | j]) 
                        states[i].push_back(j);
                }
            }
            memset(dp, 0, sizeof dp);
            dp[0][0] = 1;
            for(int i = 1; i <= m; i++) {
                for(int j = 0; j < 1 << n; j++) {
                    for(auto k : states[j]) {
                        dp[i][j] += dp[i - 1][k];
                    }
                }
            }
            cout << dp[m][0] << endl;
        }
        return 0;
    }
    

    1064. 小国王

    • 状态表示:(dp[i][j][s]) 表示当前枚举到第(i)行,已经用了(k)个棋子,前一行棋子摆放的状态是s, 如果摆放记为1否则记为0,所形成的的十进制数
    • 合法方案:
      1. (i - 1)行内部不能有两个1相邻
      2. (i - 1)行和第(i)行之间不能互相攻击到
    • 状态计算:
      已经摆完前(i)排,第(i)排状态为(a),第(i - 1)排状态为(b),已经摆了(j)个国王的所有方案。
      已经摆完前(i - 1)排,第(i - 1)排状态为(b),已经摆了(j - count(a))个国王的所有方案, (dp[i - 1][j - count(a)][b])
    #include <iostream>
    #include <cstring>
    #include <vector>
    
    using namespace std;
    
    typedef long long LL;
    const int N = 12, M = 1 << 10, K = 110;
    LL dp[N][K][M];
    int cnt[M], n, m;
    vector<int> h[M];
    vector<int> states;
    
    // 判断每行中是否中所有合法的状态:不含有相邻的不为1的数
    bool check(int state) {
        for(int i = 0; i < n; i++) 
            if((state >> i & 1) && (state >> i + 1 & 1)) 
                return false;
        return true;
    }
    
    // 计算该状态在一行中放置的棋子数量
    int count(int state) {
        int res = 0;
        for(int i = 0; i < n; i++) res += (state >> i & 1);
        return res;
    }
    
    
    int main() {
        cin >> n >> m;
        
        // 预处理各种可行的状态,存入states数组
        for(int i = 0; i < 1 << n; i++) {
            if(check(i)) {
                states.push_back(i);
                cnt[i] = count(i);
            }
        }
        
        // 枚举可行的状态,将两者可以作为上下行的存入h数组
        for(int i = 0; i < states.size(); i++) {
            for(int j = 0; j < states.size(); j++) {
                int a = states[i], b = states[j];
                if((a & b) == 0 && check(a | b)) 
                    h[i].push_back(j);
            }
        }
        // 什么都没放的方案数为1
        dp[0][0][0] = 1;
        // 枚举每行
        for(int i = 1; i <= n + 1; i ++) {
            // 枚举所有的棋子
            for(int j = 0; j <= m; j++) {
                // 遍历所有的可行的状态
                for(int a = 0; a < states.size(); a++) {
                    for(auto b : h[a]) {
                        int c = cnt[states[a]];
                        if(j >= c) 
                            dp[i][j][a] += dp[i - 1][j - c][b];
                    }
                }
            }
        }
        cout << dp[n + 1][m][0];
        return 0;
    }
    

    292. 炮兵阵地

    • 状态表示:(dp[i][j][k]) 表示枚举到第(i)行,其上一行摆放的状态是(j), 上两行的状态为(k)
    • 状态计算:如果条件合法,(dp[i][j][k] = max(dp[i - 1][j][l] + cnt[states[l]], dp[i][j][k]))
    • 判断条件:
      1. 当前为平地
      2. 当前行、当前行的上一层、当前行的上两层,之间不能有交集
    #include <iostream>
    #include <vector>
    
    using namespace std;
    const int N = 110, M = 12;
    int g[N], cnt[1 << M];
    int n, m;
    int dp[N][1 << M][1 << M];
    
    vector<int> states;
    vector<int> h[1 << M];
    // 行与行之间不能有交集
    bool check(int state) {
        for(int i = 0; i < m; i++)
            if((state >> i & 1) && ((state >> i + 1 & 1) || (state >> i + 2 & 1)))
                return false;
        return true;
    }
    
    int count(int state) {
        int res = 0;
        for(int i = 0; i < m; i++) res += (state >> i & 1);
        return res;
    }
    
    int main() {
        cin >> n >> m;
        for(int i = 1; i <= n; i++) {
            for(int j = 0; j < m; j++) {
                char c; cin >> c;
                // 此处为山地,不能摆放炮团,标记为1
                g[i] += (c == 'H') << j;
            }
        }
        for(int i = 0; i < 1 << m; i++)
            if(check(i)) {
                states.push_back(i);
                cnt[i] = count(i);
            }
            
    
        for(int i = 1; i <= n + 2; i++)
            // 枚举i行状态
            for(int j = 0; j < states.size(); j++)
                // i - 1状态
                for(int k = 0; k < states.size(); k++)
                    // i - 2状态
                    for(int l = 0; l < states.size(); l++) {
                        int curr = states[j], r1 = states[k], r2 = states[l];
                        if((r1 & r2) | (r1 & curr) | (curr & r2)) continue;
                        if((g[i] & curr) | (g[i - 1] & r1))  continue;
                        dp[i & 1][j][k] = max(dp[i & 1][j][k], dp[i - 1 & 1][k][l] + cnt[curr]);
                    }
        cout << dp[n + 2 & 1][0][0];
        return 0;
    }
    
    1. 集合类状态压缩DP

    91. 最短Hamilton路径

    • 将途径的点的状态压缩,途径的点记为1,未途径为0
    • (dp[i][j]) 表示到达(j)这个点时,最短的路径长度
    • 状态计算:(dp[i][j] = min(dp[i][j], dp[i - (1 << j)][k] + a[k][j])):
      找到更短的路径,从(k)转移过来,应该不途径第(j)个点,把j从经过的点集中去掉再加上从(k)(j)的距离
    // 初始化
    memset(dp, 0x3f, sizeof dp);
    // 开始,途径第零个点
    dp[1][0] = 0;
    for(int i = 0; i < 1 << n; i++) 
        for(int j = 0; j < n; j++) 
            // 如果经过这个点的话
            if(i >> j & 1) {
                for(int k = 0; k < n; k++) {
                    if(((i - (1 << j)) >> k) & 1) 
                        dp[i][j] = min(dp[i][j], dp[i - (1 << j)][k] + a[k][j]);
                }
            }
    cout << dp[(1 << n) - 1][n - 1];
    

    524. 愤怒的小鸟

  • 相关阅读:
    【纯水题】POJ 1852 Ants
    【树形DP】BZOJ 1131 Sta
    【不知道怎么分类】HDU
    【树形DP】CF 1293E Xenon's Attack on the Gangs
    【贪心算法】CF Emergency Evacuation
    【思维】UVA 11300 Spreading the Wealth
    【树形DP】NOI2003 逃学的小孩
    【树形DP】BZOJ 3829 Farmcraft
    【树形DP】JSOI BZOJ4472 salesman
    【迷宫问题】CodeForces 1292A A NEKO's Maze Game
  • 原文地址:https://www.cnblogs.com/Hot-machine/p/13335575.html
Copyright © 2011-2022 走看看