AcWing 896 最长上升子序列 II

题目描述:

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

输入格式

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

输出格式

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

数据范围

1≤N≤100000,−10^9≤数列中的数≤10^9

输入样例:

7
3 1 2 1 8 5 6

输出样例:

4

分析:

LIS问题用一般动态规划的思想求解复杂度是平方级别的,本题n的范围是十万,无法承受。分析下样例:3 1 2 1 8 5 6,遍历到3,得到长度为1的LIS 3,遍历到1,得到长度为1的LIS 1,我们需要的是最长上升子序列,末字符越小,上升子序列长度越容易扩展。所以只需要保留末字符较小的那个长度为1的以1为结尾的序列,遍历到2,得到1 2,在此遍历到1,无法更新任何长度的上升子序列。遍历到8,得到1 2 8,遍历到5,5可以接到序列1 2的后面,从而将长度为3的上升子序列末尾字符更新为5,即1 2 5,最后遍历到6,得到1 2 5 6,LIS的长度为4。这给我们的启示是,每个长度的LIS,想要扩展仅取决于末字符的大小,我们只需保留末字符最小的那个序列即可。

观察上面的求解过程,我们始终维持着一个数组,存储着各个长度上升子序列中末字符最小的那个字符,且该数组是单调递增的。假设某时刻该数组是1 3 2,说明长度为2的子序列末尾是3,而长度为3的子序列末尾是2;既然存在长度为3的上升子序列末字符是2,则该序列的第二个字符必然小于2,即存在末字符小于2的长度为2的上升子序列,故长度为2的子序列末尾最小字符不可能是3,假设不成立,故该数组递增。而遍历序列的过程,就是不断往该数组里替换或者插入元素的过程,如果新元素大于数组中所有元素,则插入数组末尾;如果数组中存在不小于新元素的数,则将数组中不小于新元素的最大数替换掉。总之,在数组里查找小于新元素的最大数的位置,将新元素放在该位置的后一个元素即可,为了处理方便,在数组开始插入一个很小的哨兵结点,以便于更新数组中第一个元素。查找的过程可以用二分实现,总的时间复杂度为O(nlogn)。

#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 100005;
int n,a[maxn],q[maxn];
int main(){
    scanf("%d",&n);
    for(int i = 0;i < n;i++)    scanf("%d",&a[i]);
    int len = 0;
    q[0] = -2e9;
    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,l + 1);
        q[l + 1] = a[i];
    }
    printf("%d\n",len);
    return 0;
}
发布了272 篇原创文章 · 获赞 26 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_30277239/article/details/103948169