AcWing 895. 最长上升子序列(线性dp)

给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。

输入格式
第一行包含整数 N。

第二行包含 N 个整数,表示完整序列。

输出格式
输出一个整数,表示最大长度。

数据范围
1≤N≤1000,
−10 ^ 9≤数列中的数≤10 ^ 9

输入样例:
7
3 1 2 1 8 5 6

输出样例:
4

最长上升子序列–LIS问题

题意:给我们一条长度为n数列,从中找到一个长度严格递增子序列(可以有间隔),并且是最长的那一个,输出长度的最大值

Sample3 1 2 1 8 5 6中最长上升子序列是1 2 5 6,输出4

LIS是一道经典的线性dp问题。

由于题中的数列是一个一维的序列,因此我们可以用一个一维的数组dp,来进行状态表示和计算。

dp[i]状态表示

(从集合的角度分析dp问题,这很重要,因为dp能够优化问题的核心在于:它能表示一个集合一类一类地进行表示,大大提高了解决问题的效率。)

集合:所有以a[i]结尾的严格单调上升子序列。

属性Max(一般还有:CountMin

综合一下:dp[i]表示所有以a[i]结尾的严格单调上升子序列长度的Max值。

dp[i]状态计算(核心:集合划分)

dp[i]表示了很多上升子序列的集合,我们用一个椭圆来表示所有以a[i]结尾的严格单调上升子序列。

目标:求取dp[i]的值(所有以a[i]结尾的严格单调上升子序列长度的Max值)

dp[i]不太好直接求,dp中有个很重要的思想是“分而治之”,我们将所有这样形式的单调上升子序列分为若干比较好算的类,最终dp[i]就是每一类的最大值取Max

集合划分依据:以最后一个不同的元素,即倒数第二个元素,可能的情况有i类(不妨设下标从1开始):a[1],a[2],...,a[i-1]不存在

  • (1)如果倒数第二个数不存在,那么其值就是1,序列之中只有a[i]一个元素。

  • (2)如果倒数第二个数是a[k](k=1,2,3,...,i-1),所有倒数第二个元素是a[k]的序列都可以分为两部分:

    • 第一部分:a[i](最后一个元素)
    • 第二部分:除了a[i]的剩余部分(最后一个元素是a[k]

微信图片_20220207113417.png

如果求其中的最大值,即Max(第二部分)+1(第一部分),我们从定义出发可以直接得出结果:dp[k] + 1

状态转移方程dp[i] = max(dp[i], dp[k] + 1)

(将dp[i]dp[k] + 1max是因为dp[i]一开始是1,即只有a[i]一个元素,dp[i]的更新是遍历a[i]前面的元素来实现的,所以当然是取两者较大值而不是直接赋值,在代码中可以得到体现)

椭圆如图所示:

微信图片_20220207113304.png

还有个要注意的点,我们划分的某一类可能会不存在,如果a[k]>=a[i]则意味着我们当前分的这一类所有序列都是不合法的,无需考虑,因此我们在划分集合的时候要特殊判断一下该类是否存在(a[k]<a[i]才存在)

分析完dp[i]的状态表示之后,所有的状态全部求完,那么答案就是:max(dp[1],dp[2],...,dp[n])

(最长上升子序列一定是以某个数结尾的,则考虑以所有数结尾的情况,再取max,这样取得的max一定是包含了所有的情况)

时间复杂度

O(n2):状态数(n) * 转移数(n)

代码

#include<bits/stdc++.h>

using namespace std;
const int N = 1010;
int a[N], dp[N];
int n;

int main()
{
    cin>>n;
    for(int i=1; i<=n; ++i) cin>>a[i];
    
    int res = 1;//最少的情况是只有一个元素,输出 1 即可
    
    for(int i=1; i<=n; ++i)
    {
        dp[i] = 1;//一开始只有a[i]一个元素
        for(int j=1; j<i; ++j)//遍历a[i]之前的元素
        {
            if(a[j]<a[i])
            {
                dp[i] = max(dp[i], dp[j]+1);//根据方程,两者取max
                res = max(res, dp[i]);//在状态计算过程中顺便更新答案
            }
        }
    }
    
    cout<<res<<endl;
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值