• 动态规划_最长上升子序列


    原型:最长上升子序列

    分析

    • 状态表示:所有以a[i]结尾的严格单调上升的子序列的Max长度
    • 状态划分依据:以最后一个不同的点
    • 状态方程:dp[i] = max(dp[i], dp[j] + 1), j必须要小于i
    for(int i = 1; i <= n; i++) {
        dp[i] = 1;
        for(int j = 1; j < i; j++) {
            if(a[i] > a[j]) {
                dp[i] = max(dp[i], dp[j] + 1);
            }
        }
    }
    int res = 0;
    for(int i = 1; i <= n; i++) res = max(res, dp[i]);
    printf("%d ", res);
    

    寻找路径

    memset(tr, -1, sizeof tr);
    
    for(int i = 1; i <= n; i++) {
        dp[i] = 1;
        for(int j = 1; j < i; j++) {
            if(a[i] > a[j]) {
                // 有更新
                if(dp[i] < dp[j] + 1) {
                    dp[i] = dp[j] + 1;
                    tr[i] = j;
                }
            }
        }
    }
    
    int ans = 0, sign = -1;
    for(int i = 1; i <= n; i++) {
        if(ans < dp[i]) {
            ans = dp[i];
            sign = i;
        }
    }
    // 找到最终的以sign为结尾,长度为ans的序列
    printf("%d %d
    ", ans, sign);
    int i = sign;
    while(~i) {
        cout << a[i] << " ";
        i = tr[i];
    }
    

    变形扩展

    1017. 怪盗基德的滑翔翼

    • 初始方向不确定,考虑从两个方向各找一遍(从左到右、从右到左)
    • 最长下降子序列可转化成逆向的最长上升子序列

    AcWing 1014. 登山

    • 求严格上升到某一节点之后严格下降这一整段的序列的长度最大值
    • 分别从左到右、从右到左求最长上升子序列长度,遍历每个节点,以此节点作为拐点,所形成的序列长度求最大值:res = max(res, f[i] + g[i] - 1)

    AcWing 1012. 友好城市

    城市布局

    • 以南边的城市为自变量,将其排序,与之对应的因变量即北边的城市坐标,应该满足跟随自变量的递增而单调递增,即抽象出最长上升子序列问题
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    const int N = 5100;
    int dp[N];
    struct city {
        int s, n;
    }cities[N];
    
    int main() {
        int n; scanf("%d", &n);
        for(int i = 1; i <= n; i++) scanf("%d%d", &cities[i].s, &cities[i].n);
        
        sort(cities, cities + n + 1, [&](city a, city b) { return a.n < b.n;});
        
        for(int i = 1; i <= n; i++) {
            dp[i] = 1;
            for(int j = 1; j < n; j++) {
                if(cities[j].n < cities[i].n) {
                    dp[i] = max(dp[i], dp[j] + 1);
                }
            }
        }
        int res = 0;
        for(int i = 1; i <= n; i++) res = max(res, dp[i]);
        printf("%d", res);
        return 0;
    }
    

    1016. 最大上升子序列和

    • 状态表示:以该元素为结尾的上升子序列的和
    for(int i = 1; i <= n; i++) {
            dp[i] = a[i];
            for(int j = 1; j < i; j++) {
                if(dp[i] > dp[j]) {
                    dp[i] = max(dp[i], dp[j] + a[i]);
                }
            }
        }
    

    1010. 拦截导弹 : LIS + 贪心

    • 第一问LIS问题
    • 第二问:求覆盖所有节点的子序列的个数的最小值
    贪心思路:
    • 从前往后扫描每个数,对于每个数有以下两种情况
      1.现有的的所有子序列的结尾的数都小于当前这个数 -> 创建一个新的子序列
      2.将当前数放到结尾大于等于这个数的最小子序列后面(别耽误别人,尽量节约 haha)
    贪心法求最长上升子序列(O(n^2))超时

    数据范围:需要(O(nlogn))

    (1 leq N leq 100000)
    (-10^9 leq 数列中的数 leq 10^9)

    • 贪心策略:尽可能使得每个子序列中的结尾元素越小越好

    • 具体措施:针对每个数,将这个数接到结尾元素最大的小于此元素的子序列后

    /*
    数组a存储所有数据
    数组q[i]表示序列长度为i的子序列的最后一个数
    */ 
    int len = 0;
    for(int i = 0; i < n; i++) {
        // 找到满足小于a[i]的最大值
        int l = 0, r = len;
        while(l < r) {
            int mid = l + r + 1 >> 1;
            if(q[mid] < a[i]) l = mid;
            else r = mid - 1;
        }
        len = max(len, r + 1);
        q[r + 1] = a[i];
    }
    cout << len;
    
    Dilworth定理
    • Dilworth定理:对于一个偏序集,最少链划分等于最长反链长度。

    • Dilworth定理的对偶定理:对于一个偏序集,其最少反链划分数等于其最长链的长度。

    以上的铺垫用于解决拦截导弹问题:转化成求最长上升子序列长度

    187. 导弹防御系统: LIS + 暴搜

    最长公共子序列:原型

    • 所有在第一个序列的前i个字母中出现,且在第二个序列的前j个字母中出现的子序列
    • Max
    • 以a[i], b[j]分别选与不选划分为四种状态
    • dp[i - 1][j - 1]
    • dp[i - 1][j]:不一定有b[j],因此包含前面的状态(在第一个序列的前i - 1个字母出现,且在第二个序列的前j - 1个字母中出现),但状态表示的是子序列的长度最大值,因此状态存在重复,不会影响最终结果
    • dp[i][j - 1]:与dp[i - 1][j]的情况类似
    • dp[i - 1][j - 1] + 1:子序列中包含a[i], a[j]
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            if(a[i] == b[j]) dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1);
        }
    }
    cout << dp[n][m];
    

    最长公共上升子序列

    • 状态表示:所有第一个序列的前i个字母,和第二个序列的前j个字母构成的, 且以b[j]结尾的公共上升子序列的Max
    • 状态计算
      1.所有不包含a[i]的公共上升子序列: dp[i - 1][j]
      2.所有包含a[i]的公共上升子序列

    对于包含a[i]的公共上升子序列,将其根据倒数第二个字母是什么进行划分:

    • b[1]:最大长度为1
    • b[2]:最大长度为dp[i - 1][1] + 1
    • ...
    • b[j - 1]: dp[i - 1][j - 1] + 1
    for (int i = 1; i <= n; i ++ ) {
        for (int j = 1; j <= n; j ++ ) {
            f[i][j] = f[i - 1][j];
            if (a[i] == b[j]) {
                int maxv = 1;
                for (int k = 1; k < j; k ++ )
                    if (a[i] > b[k])
                        maxv = max(maxv, f[i - 1][k] + 1);
                f[i][j] = max(f[i][j], maxv);
            }
        }
    }
    

    从中发现,每次如果找到公共子序列之后,都要计算一下dp[i][1 ~ j - 1]

    for (int i = 1; i <= n; i ++ ) {
        int maxv = 1;
        for (int j = 1; j <= n; j ++ ) {
            f[i][j] = f[i - 1][j];
            if (a[i] == b[j]) f[i][j] = max(f[i][j], maxv);
            if (a[i] > b[j]) maxv = max(maxv, f[i - 1][j] + 1);
        }
    }
    
    int res = 0;
    for (int i = 1; i <= n; i ++ ) res = max(res, f[n][i]);
    printf("%d
    ", res);
    
  • 相关阅读:
    Mvc 简单分页代码
    算法
    atx
    Java8函数式编程(A)
    axios
    props
    vue 的keep alive使用注意项
    android帮助
    testng监听器方法执行顺序
    常用正则表达式
  • 原文地址:https://www.cnblogs.com/Hot-machine/p/13246490.html
Copyright © 2020-2023  润新知