最长递增子序列的基础与理解

本文讲述了如何通过贪心和二分搜索优化求解最长递增子序列问题,强调理解序列结构的重要性,并给出了一个实际应用实例,如CFround908D题的解法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最长递增子序列的原型题目

力扣求最长递增子序列的长度
此处dp做的话是O(n*n)的时间复杂度所以不过多解释,
主要是贪心和二分搜索
但是在讲解方法之前需要先将这个序列数中的最长递增子序列的理解给理解透彻了,不然无法彻底明白这个方法

对于最长递增子序列的理解

最长递增子序列称为序列c,原序列称为序列a,
c i 与相邻的 c i + 1 之间的一个左开右闭的一个区间里 c i + 1 是比 c i 大而且在大小上距离 c i 最近的一个数 c_{i}与相邻的c_{i+1}之间的一个左开右闭的一个区间里c_{i+1}是比c_{i}大而且在大小上距离c_{i}最近的一个数 ci与相邻的ci+1之间的一个左开右闭的一个区间里ci+1是比ci大而且在大小上距离ci最近的一个数

如此说来,对于每一个特定长度的来源于a序列的c序列,都有一个末尾元素最小的c序列,比如说力扣题解里举的那个例子0 8 4 12 2对于长度为1的c序列末尾元素最小的是0,长度为2的c序列,末尾元素最小的是0 2序列,三的话是0 8 12或者0 4 12

由此可得对于每一个特定的长度k,我们可以确定一个或多个序列c,使其末尾元素为可以在保证长度为k的时候所能达到的最小值,对于这个特定长度k所得到的这个c序列就是我们得到的最有利于接下来延长的递增子序列。
如果上述的总结你看懂了, 那么你要么是有基础,要么是理解能力到位,如果没懂,还是去力扣找这个题的题解或者看这个知乎博主pecco的解答
每一个 a i 元素,都可以领起一支递增子序列,最短的是它本身,我们每一插入一个 a i 到 c 序列里面时,都可以通过二分查找判断如果以 a i 为末尾元素领起一支递增子序列的话 他的长度最长的是多少,我懒得解释了,自己看我发上去的网站吧。 每一个a_{i}元素,都可以领起一支递增子序列,最短的是它本身,我们每一插入一个a_{i}到\\c序列里面时,都可以通过二分查找判断如果以a_{i}为末尾元素领起一支递增子序列的话\\他的长度最长的是多少,我懒得解释了,自己看我发上去的网站吧。 每一个ai元素,都可以领起一支递增子序列,最短的是它本身,我们每一插入一个aic序列里面时,都可以通过二分查找判断如果以ai为末尾元素领起一支递增子序列的话他的长度最长的是多少,我懒得解释了,自己看我发上去的网站吧。
解释一大堆,最有价值的还是我贴上去的那两个网站(看别人的解释)

对于最长递增子序列的练习

链接: VJ上的练习,还未提交过,力扣上的已经提交过了

最长递增子序列理解的应用

CF round 908 D题

主要思路

找到a数列原本的最长递增子序列,然后发现他的最小值和最大值(或其位置),然后将b数列降序排列,然后把b数列分成两半,一部分放在a数列最长递增子序列前面,一部分放在a数列最长递增子序列后面。

具体实现

完整实现代码如下

#include "bits/stdc++.h"
using namespace std;
using LL=long long;
const int N = 200100;

bool cmp(int a, int b) {
    return a > b;
}

void solve() {
    int n, m;
    int a[N], b[N];
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for (int i = 1; i <= m; i++) {
        cin >> b[i];
    }
    sort(b + 1, b + 1 + m, greater<LL>());// 降序排序
    LL idx = 1;
    while (idx <= m && b[idx] >= a[1]) // 大的放前面
    {
        cout << b[idx++] << ' ';
    }
    for (int i = 1; i <= n; i++) {
        cout << a[i] << ' ';
        while (idx <= m && a[i] >= b[idx] && b[idx] >= a[i + 1]) // 不多加升序即可
            cout << b[idx++] << ' ';
    }
    for (int i = idx; i <= m; i++) // 小的放后面
    {
        cout << b[i] << ' ';
    }
    cout << '\n';
}

int main() {
    int num = 1;
    cin >> num;
    while (num--)
        solve();
    return 0;
}

部分实现代码解析

while (idx <= m && b[idx] >= a[1]) // 大的放前面
    {
        cout << b[idx++] << ' ';
    }
    for (int i = 1; i <= n; i++) {
        cout << a[i] << ' ';
        while (idx <= m && a[i] >= b[idx] && b[idx] >= a[i + 1]) // 不多加升序即可
            cout << b[idx++] << ' ';
    }
    for (int i = idx; i <= m; i++) // 小的放后面
    {
        cout << b[i] << ' ';
    }

这部分是关键代码段,它规定了输出的顺序。

要知道,出现比原a数列中最长递增子序列长度更长的子序列的话,有三种情况,情况A:有可能在b数列内部出现,情况B和C:有可能在a数列原有的最长递增子序列基础上在开头增加长度,或者在尾部增长,但是第一种情况只需要一种降序排序可以解决。

首先一个while函数,让大于A数列第一个元素的所有b数列的元素全部降序输出,然后因为最长递增子序列的性质,导致
a 1 必然会小于等于 c 1 a_{1}必然会小于等于c_{1} a1必然会小于等于c1
因此,这样一来可以避免情况B的发生,但是还要避免情况C的发生。
于是有了接下来的for循环,for循环同样是利用了最长递增子序列的性质
使得当 b i > = c 1 且 b i < = c k ( c 的长度就是 k )时,先令 c j 是不大于 b i 的最大 c 序列中元素, b i 必然不会出现在 c j 的后面,使得情况 C 不会发生 使得当b_i>=c_1且b_i<=c_k(c的长度就是k)时,先令c_j是不大于b_i的最大c序列中元素,\\b_i必然不会出现在c_j的后面,使得情况C不会发生 使得当bi>=c1bi<=ck(c的长度就是k)时,先令cj是不大于bi的最大c序列中元素,bi必然不会出现在cj的后面,使得情况C不会发生

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值