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. 愤怒的小鸟

  • 相关阅读:
    javascript--setTimeout定时器
    javascript--BOM的onload事件和onunload事件
    JavaScript--文本框中只允许输入数字的操作(其他字符不显示)
    JavaScript--获取页面盒子中鼠标相对于盒子上、左边框的坐标
    javascript--事件对象e的来源、意义、应用及其属性的用法 function(e){}
    javascript--select标签的添加删除功能的使用
    JavaScript--动态添加元素(纯js书写table并删除数据)
    MyBatis快速入门
    《大型网站技术架构》学习笔记-01概述
    FreeMarker快速入门
  • 原文地址:https://www.cnblogs.com/Hot-machine/p/13335575.html
Copyright © 2011-2022 走看看