最长上升子序列(DP)

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

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

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

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

数据范围
1≤N≤100000,
−109≤数列中的数≤109
输入样例:

7
3 1 2 1 8 5 6

输出样例:

4

基本思路

首先,求最长上升子序列(后面称 LIS )的问题和它的子问题有这样的依赖关系:

 

上图这样就可以求得序列3 1 2 1 8 5 6的 LIS 长度是 4 ,把6接在1 2 5后面即可。
所以,要求串a[0...i]的 LIS ,就要知道:串a[0...i-1]中各长度的上升子序列末尾元素的最小值。

后者可以用一个q数组来存储,q[i]是所有长度为i的上升子序列末尾元素的最小值。这个数组是严格单调递增的(原因不赘述),所以每次只要用二分查找,在O(logn)O(logn)的时间内就能从串a[0...i-1]对应的p数组求得串a[0...i]的 LIS ,完成状态转移。

q 数组的维护
接下来的难题就是如何在求得串a[0...i]的 LIS 后更新q数组,好让下一个到串a[0...i+1]的状态转移顺利发生。在下面的代码里面可以看到,实际上在每轮循环只需要做一件事:将本次发现的 LIS 的末尾元素赋值给q[l+1]。为什么这么简单?有两点问题:

注意这里是直接赋值,而不是求min,为什么a[i]一定不大于q[l+1]?
为什么只修改q[l+1]这一项?其他项一定不需要更新吗?
画个图(1.)就很清楚了,由于我们是二分搜索搜到的l,所以a[i]一定是严格大于q[l],而小于等于q[l+1]。

 

对于(2.),首先由于当前串的 LIS 长度就只有l+1,所以q[l+1]后面的项肯定不会被更新。对于q[1...l],看这个例子就很容易明白了:

 

#include<bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int a[N],q[N];
int n;

int main()
{
    cin>>n;
    for(int i = 0; i < n; i++)scanf("%d",&a[i]);
    int len = 0;
    for(int i = 0; i < n; 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<<endl;
    return 0;
}


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值