最长上升子序列(线性dp/LIS)专题总结

引言

本来大一就学了的线性dp, 正好这几天遇到了这样的问题也忘得差不多了, 而且学弟们也都在学这个内容, 我花了一天多的时间刷LIS的题, 写一写我刷这种题的感想

正章

首先我们要知道LIS问题是线性dp的一种, 他们的状态之间存在线性关系
通俗来讲, 我们设状态1为 f 1 f_1 f1, 状态2为 f 2 f_2 f2则他们之间满足
f 2 f_2 f2= k ∗ f 1 + b k*f_1+b kf1+b
在最长上升子序列问题中, 因为是上升子序列, 要求子序列严格单调递增, 则有 f 2 f_2 f2= k ∗ f 1 k*f_1 kf1 ( k k k>0)
明白了线性关系后我们来看LIS问题的分类
LIS问题分为四种:

最长上升子序列, 最长下降子序列, 最长不上升子序列, 最长不下降子序列

那么先从最长上升子序列开始讲起

最长上升子序列

例题

上例题:
在这里插入图片描述
对于初学者来说最长上升子序列问题必须先暴力枚举 O ( n 2 ) O(n^2) O(n2)如何解决

解法

双重循环枚举, 以i为结尾(或者开头)的子序列最长为多长

for (int i = 0; i < n; i++) 
{
        f[i] = 1;//初始化为1, 因为最差自己本身只算1位
        for (int j = 0; j < i; j++)//以i为结尾暴力搜索
            if (a[i] > a[j]) f[i] = max(f[i], f[j] + 1);
        //状态转移: 出现有比结尾更小的数条件符合 则比较条件符合的数的LIS序列
}

这种方法不必多解释, 在稍微难一点的题就会把暴力范围给卡了
所以接下来要说优化的方法, 二分优化
此处使用库函数二分, 库函数二分的运用在此处很重要(不然你就手写二分吧)
借用一下别人博客的图
在这里插入图片描述
那么优化怎么写呢?
首先, 我们要理解最长上升子序列的核心思想在于, 是否加入这个元素, 只与序列当前的最后一个有关系(分别对应 > < > < >< > = >= >= < = <= <=), 那么我们就能用栈来模拟这一个过程(先进先出), 以栈的长度来表示最长上升子序列的答案
但要实现一个最长上升子序列, 只知道这个是不够的, 当目前的这个元素小于当前序列的最后一个值时我们还需要更新前面的值, 举个例子
对于求 a a a数组的LIS, a={7 10 12 8 9 11}当 i = 4 i=4 i=4时(下标从1开始), LIS序列此时为7 10 12不可能在末尾放入1, 那么将10改为1, 这样做不会影响答案, 因为栈的长度并没有改变, 若是出现了以8为起点的LIS > 以10为起点的LIS时还能确保栈顶的元素能够继续加入新的元素, 如果不这么做就会出现最终答案为7 10 12而不是7 8 9 11
搜索第一个大于等于当前数使用二分实现, 因为栈中的正好是顺序的上升序列怎么可能无序呢
那么对于这个

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int main(void) {
    int n; cin >> n;
    vector<int>arr(n);
    for (int i = 0; i < n; ++i)cin >> arr[i];

    vector<int>stk;//模拟堆栈
    stk.push_back(arr[0]);

    for (int i = 1; i < n; ++i) {
        if (arr[i] > stk.back())//如果该元素大于栈顶元素,将该元素入栈
            stk.push_back(arr[i]);
        else//替换掉第一个大于或者等于这个数字的那个数
            *lower_bound(stk.begin(), stk.end(), arr[i]) = arr[i];
    }
    cout << stk.size() << endl;
    return 0;
}

作者:233
链接:https://www.acwing.com/solution/content/3783/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

代码来自acwing

关于代码中库函数二分的解释

*lower_bound(stk.begin(), stk.end(), arr[i])相当于容器中的这个变量, 可以直接被赋值修改

最长下降子序列, 最长不上升子序列, 最长不下降子序列

这三个换汤不换药, 也就是库函数二分的问题

对于最长下降子序列, 因为是寻找比栈顶更小的元素, 那么栈应该是倒序的, 二分的时候也应该倒序搜
具体方法有两种

lower_bound(stk.begin(), stk.end(), arr[i],greater<int>());//greater结构
lower_bound(stk.rend(), stk.rbegin(), arr[i]);//倒序迭代器

所以if条件改成小于栈顶, 二分改成倒序就好了

对于最长不上升子序列, 最长不上升也就是 < = <= <=, 栈中允许存在相同的元素
if要修改为 < = <= <=, 二分改为upper_bound
最长不下降同理, 如果你听懂了上面所说的你一定能推理出这个问题

#推荐习题
最长上升子序列
怪盗基德的滑翔翼
登山
拦截导弹
友好城市
合唱队形
最大上升子序列
最少拦截系统

  • 7
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值