问题描述
求数列 A = a 0 , a 1 , . . . , a n − 1 A=a_0,a_1,...,a_{n-1} A=a0,a1,...,an−1的最长递增子序列(LIS)。如果数列A的子序列 a i 0 , a i 1 , . . . , a i k a_{i_0},a_{i_1},...,a_{i_k} ai0,ai1,...,aik满足0 ≤ i 0 i_0 i0 ≤ i 1 i_1 i1 ≤ … ≤ i k i_k ik ≤ n且 a i 0 a_{i_0} ai0 ≤ a i 1 a_{i_1} ai1 ≤ … ≤ a i k a_{i_k} aik,则称该子序列为数列A的递增子序列。其中k值最大的子序列称为最长递增子序列。
输入:
第1行输入表示数列A长度的整数n。接下来n行输入数列各元素
a
i
a_i
ai
输出:
输出最长递增子序列的长度,占1行
限制:
1 ≤ n ≤ 100000
0 ≤
a
i
a_i
ai ≤
1
0
9
10^9
109
输入示例
5
5
1
3
2
4
输出示例
3
讲解
长度为n的数列A有多达 2 n 2^n 2n个子序列,但我们应用动态规划法仍然可以很高效地求出最长递增子序列(LIS)。这里介绍两个算法:
先考虑用下列变量设计动态规划的算法。这里输入数列的第一个元素为A[1]。
L[n+1]:一维数组,L[i]为由A[1]到A[i]中的部分元素构成且最后选择了A[i]的LIS的长度
P[n+1]:一维数组,P[i]为由A[1]到A[i]中的部分元素构成且最后选择了A[i]的LIS的倒数第二个元素的位置(记录当前已得出的最长递增子序列中,各元素前面一个元素的位置)。
有了这些变量,动态规划法求LIS的算法可以这样实现:
动态规划法求最长递增子序列的算法:
LIS()
L[0] = 0
A[0] = 0 //选择小于A[1]到A[n]中任意一个数的值进行初始化
P[0] = -1
for i = 1 to n
k = 0
for j = 0 to i - 1
if A[j] < A[i] && L[j] > L[k]
k = j
L[i] = L[k] + 1 //满足A[j] < A[i]且L[j]最大的j即为k
P[i] = k //LIS中A[i]的前一个元素为A[k]
上述动态规划法的复杂度为 O ( n 2 ) O(n^2) O(n2),仍不很理想
实际上,只要把动态规划法与二分搜索结合起来,就能进一步提高效率。这种算法需要下述变量:
L[n]:一维数组,L[i]表示长度为 i + 1的递增子序列的末尾元素最小值
l
e
n
g
t
h
i
length_i
lengthi:整数,表示前 i 个元素构成的最长递增子序列的长度。
由于我们是按照升序记录的L[j],因此可以通过二分搜索来求L[j](j = 0到length-1)中第一个大于等于A[i]的元素的下标j。这个兼用了二分搜索和动态规划的算法复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),实现如下:
兼用动态规划法和二分搜索求最长递增子序列:
LIS()
L[0] = A[0]
length = 1
for i = 1 to n-1
if L[length] < A[i]
L[length++] = A[i]
else
L[j] (j = 0, 1, ..., length-1)中第一个大于等于A[i]的元素 = A[i]
AC代码如下
#include<iostream>
#include<algorithm>
#define MAX 100000
using namespace std;
int n, A[MAX+1], L[MAX];
int lis() {
L[0] = A[0];
int length = 1;
for(int i = 1; i < n; i++) {
if(L[length-1] < A[i]) {
L[length++] = A[i];
} else {
*lower_bound(L, L + length, A[i]) = A[i];
}
}
return length;
}
int main(){
cin>>n;
for(int i = 0; i < n; i++) {
cin>>A[i];
}
cout<<lis()<<endl;
return 0;
}