题目描述:
给定一个长度为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;
}