给定一个长度为 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;
}