当前位置: 首页 > news >正文

NO.85十六届蓝桥杯备战|动态规划-经典线性DP|最长上升子序列|合唱队形|最长公共子序列|编辑距离(C++)

经典线性dp问题有两个:最⻓上升⼦序列(简称:LIS)以及最⻓公共⼦序列(简称:LCS),这两道题⽬的很多⽅⾯都是可以作为经验,运⽤到别的题⽬中。⽐如:解题思路,定义状态表⽰的⽅式,推到状态转移⽅程的技巧等等。
因此,这两道经典问题是⼀定需要掌握的

B3637 最长上升子序列 - 洛谷
  1. 状态表⽰
    dp[i]表⽰:以i 位置元素为结尾的「所有⼦序列」中,最⻓递增⼦序列的⻓度。
    最终结果就是整张dp 表⾥⾯的最⼤值。
  2. 状态转移⽅程:
    对于dp[i] ,我们可以根据「⼦序列的构成⽅式」,进⾏分类讨论:
  • ⼦序列⻓度为1 :只能⾃⼰玩了,此时dp[i] = 1
  • ⼦序列⻓度⼤于1 :a[i]可以跟在前⾯某些数后⾯形成⼦序列。设前⾯的某⼀个数的下标为j,其中 1 ≤ j < i 1 \le j < i 1j<i。只要a[j] < a[i],i位置元素跟在j元素后⾯就可以形成递增序列,⻓度为dp[j]+1
    因此,我们仅需找到满⾜要求的最⼤的dp[j] + 1即可。
    综上,dp[i] = max(dp[j] + 1, dp[i]) ,其中1 ≤ j < i && nums[j] < nums[i]
  1. 初始化:
    不⽤单独初始化,每次填表的时候,先把这个位置的数改成1 即可。
  2. 填表顺序:
    显⽽易⻅,填表顺序「从左往右」
#include <bits/stdc++.h>
using namespace std;

const int N = 5010;

int n;
int a[N];
int f[N];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    
    int ret = 1;
    for (int i = 1; i <= n; i++)
    {
        f[i] = 1;

        for (int j = 1; j < i; j++)
        {
            if (a[j] < a[i])
            {
                f[i] = max(f[i], f[j] + 1);
            }
        }
        ret = max(ret, f[i]);
    }
    cout << ret << endl;
    
    return 0;
}
最长上升子序列2

利⽤贪⼼+⼆分优化动态规划:

  • 我们在考虑最⻓递增⼦序列的⻓度的时候,其实并不关⼼这个序列⻓什么样⼦,我们只是关⼼最后⼀个元素是谁。这样新来⼀个元素之后,我们就可以判断是否可以拼接到它的后⾯。
  • 因此,我们可以创建⼀个数组,统计⻓度为 x 的递增⼦序列中,最后⼀个元素是谁。为了尽可能的让这个序列更⻓,我们仅需统计⻓度为 x 的所有递增序列中最后⼀个元素的「最⼩值」。
  • 统计的过程中发现,数组中的数呈现「递增」趋势,因此可以使⽤「⼆分」来查找插⼊位置
#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;

int n;
int a[N];
int f[N], len;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    
    for (int i = 1; i <= n; i++)
    {
        if (len == 0 || a[i] > f[len]) f[++len] = a[i];
        else
        {
            //二分插入位置
            int l = 1, r = len;
            while (l < r)
            {
                int mid = (l + r) / 2;
                if (f[mid] >= a[i]) r = mid;
                else l = mid + 1;
            }
            f[l] = a[i];
        }
    }
    cout << len << endl;
    
    return 0;
}
P1091 [NOIP 2004 提高组] 合唱队形 - 洛谷

对于每⼀个位置i ,计算:

  • 从左往右看:以i 为结尾的最⻓上升⼦序列f[i]
  • 从右往左看:以i 为结尾的最⻓上升⼦序列g[i]
    最终结果就是所有f[i] + g[i] - 1⾥⾯的最⼤值
#include <bits/stdc++.h>
using namespace std;

const int N = 110;

int n;
int a[N];
int f[N], g[N];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];
    
    // 从左往右
    for(int i = 1; i <= n; i++)
    {
        f[i] = 1;
        for(int j = 1; j < i; j++)
        {
            if(a[j] < a[i])
            {
                f[i] = max(f[i], f[j] + 1);
            }
        }
    }
    
    // 从右往左
    for(int i = n; i >= 1; i--)
    {
        g[i] = 1;
        for(int j = n; j > i; j--)
        {
            if(a[j] < a[i])
            {
                g[i] = max(g[i], g[j] + 1);
            }
        }
    }
    
    int ret = 0;
    for(int i = 1; i <= n; i++)
    {
        ret = max(ret, f[i] + g[i] - 1);
    }
    
    cout << n - ret << endl;
    
    return 0;
}
牛可乐和最长公共子序列
  1. 状态表⽰:
    dp[i][j]表⽰:s1的[1,i]区间以及s2的[1,j]区间内的所有的⼦序列中,最⻓公共⼦序列的
    ⻓度。
    那么dp[n][m]就是我们要的结果。
  2. 状态转移⽅程:
    对于dp[i][j] ,我们可以根据s1[i]s2[j]的字符分情况讨论:
    a. 两个字符相同s1[i] = s2[j]:那么最⻓公共⼦序列就在s1的[1, i - 1]以及s2的[1, j - 1]区间上找到⼀个最⻓的,然后再加上s1[i]即可。因此dp[i][j] = dp[i - 1][j - 1] + 1
    b. 两个字符不同s1[i] != s2[j]:那么最⻓公共⼦序列⼀定不会同时以s1[i]s2[j]结尾。那么我们找最⻓公共⼦序列时,有下⾯三种策略:
  • 去s1 的[1, i - 1]以及s2的[1, j]区间内找:此时最⼤⻓度为dp[i - 1][j]
  • 去s1 的[1, i]以及s2 的[1, j - 1]区间内找:此时最⼤⻓度为dp[i][j - 1]
  • 去s1 的[1, i - 1]以及s2 的[1, j - 1]区间内找:此时最⼤⻓度为dp[i - 1][j - 1]
    我们要三者的最⼤值即可。但是我们仔细观察会发现,第三种包含在第⼀种和第⼆种情况⾥⾯,但是我们求的是最⼤值,并不影响最终结果。因此只需求前两种情况下的最⼤值即可。
    综上,状态转移⽅程为:
    if(s1[i] = s2[j]) dp[i][j] = dp[i - 1][j - 1] + 1 ;
    if(s1[i] != s2[j]) dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
  1. 初始化:
    直接填表即可。
  2. 填表顺序:
    根据「状态转移⽅程」得:从上往下填写每⼀⾏,每⼀⾏从左往右
#include <bits/stdc++.h>
using namespace std;

const int N = 5010;

string s, t;
int f[N][N];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    while (cin >> s >> t)
    {
        int n = s.size(), m = t.size();
        
        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j <= m; j++)
            {
                if (s[i - 1] == t[j - 1]) f[i][j] = f[i-1][j-1] + 1;
                else f[i][j] = max(f[i-1][j], f[i][j-1]);
            }
        }
        cout << f[n][m] << endl;
    }
    
    
    return 0;
}
P2758 编辑距离 - 洛谷

两个字符串之间的dp 问题,与最⻓公共⼦序列的分析⽅式类似。

  1. 状态表⽰:
    dp[i][j] 表⽰:字符串A 中[1, i] 区间与字符串B 中[1, j] 区间内的编辑距离。
    那么dp[n][m] 就是我们要的结果
  2. 状态转移⽅程:
    对于dp[i][j] ,我们可以根据A[i]B[j] 的字符分情况讨论:
    a. 两个字符相同A[i] = B[j] :那么dp[i][j]就是A的[1, i - 1]以及B的[1, j - 1]区间内编辑距离dp[i][j] = dp[i - 1][j - 1],因此;
    b. 两个字符不同A[i] != B[j] :那么对于A 字符串,我们可以进⾏下⾯三种操作:
  • 删掉A[i]:此时dp[i][j]就是A的[1, i - 1]以及B的[1, j]区间内的编辑距离,因此dp[i][j] = dp[i - 1][j] + 1
  • 插⼊⼀个字符:在字符串A的后⾯插⼊⼀个B[j],此时的dp[i][j]就是A的[1, i]以及B的[1, j - 1]区间内的编辑距离,因此dp[i][j] = dp[i][j - 1] + 1
  • A[i]替换成B[j]:此时的dp[i][j]就是A的[1, i - 1]以及B的[1, j - 1]区间内的编辑距离,因此dp[i][j] = dp[i - 1][j - 1] + 1
    我们要三者的最⼩值即可。
  1. 初始化:
    需要注意,当i,j等于0的时候,这些状态也是有意义的。我们可以全部删除,或者全部插⼊让
    两者相同。
    因此需要初始化第⼀⾏dp[0][j] = j (1 ≤ j ≤ m) ,第⼀列dp[i][0] = i (1 ≤ i ≤ n)
  2. 填表顺序:
    初始化完之后,从[1, 1] 位置开始从上往下每⼀⾏,每⼀⾏从左往右填表即可
#include <bits/stdc++.h>
using namespace std;

const int N = 2010;

string a, b;
int n, m;
int f[N][N];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> a >> b;
    n = a.size(); m = b.size();
    a = " " + a; b = " " + b;

    //初始化
    for (int i = 1; i <= n; i++) f[i][0] = i;
    for (int j = 1; j <= m; j++) f[0][j] = j;

    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            if (a[i] == b[j]) f[i][j] = f[i-1][j-1];
            else f[i][j] = min(min(f[i-1][j], f[i-1][j-1]), f[i][j-1]) + 1;
        }
    }
    cout << f[n][m] << endl;
    
    return 0;
}

相关文章:

  • FreeRTOS入门与工程实践-基于STM32F103(一)(单片机程序设计模式,FreeRTOS源码概述,内存管理,任务管理,同步互斥与通信,队列,信号量)
  • BGP分解实验·23——BGP选路原则之路由器标识
  • 最新版IDEA超详细图文安装教程(适用Mac系统)附安装包及补丁2025最新教程
  • 首批 | 云轴科技ZStack通过电子标准院云上部署DeepSeek验证测试
  • Tkinter高级布局与窗口管理
  • Node.js中util模块详解
  • 【golang/jsonrpc】go-ethereum中json rpc初步使用(websocket版本)
  • vue3使用keep-alive缓存组件与踩坑日记
  • [实战] 二分查找与哈希表查找:原理、对比与C语言实现(附完整C代码)
  • PostgreSQL 实例运行状态全面检查
  • 考研数据结构精讲:数组与特殊矩阵的压缩存储技巧(包含真题及解析)
  • 大数据面试问答-Hadoop/Hive/HDFS/Yarn
  • 基于SpringBoot汽车零件商城系统设计和实现(源码+文档+部署讲解)
  • vue3+nodeJs+webSocket实现聊天功能
  • stack overflow国内无法访问原因
  • 中文编码,GB系列,UTF
  • 正则表达式使用知识(日常翻阅)
  • 基于频率约束条件的最小惯量需求评估,包括频率变化率ROCOF约束和频率最低点约束matlab/simulink
  • 探索 Rust 语言:高效、安全与并发的完美融合
  • hashcode() equals()
  • 经济日报金观平:充分发挥超大规模市场优势
  • 工程院院士葛世荣获聘任为江西理工大学校长
  • 烈士沈绍藩遗孤、革命家帅孟奇养女舒炜逝世,享年96岁
  • 江西暴雨强对流明显,专家:落雨区高度重叠,地质灾害风险高
  • 山寨“小米”智能马桶、花洒销售额过亿,被判赔3500万元
  • 少年中国之少年的形塑