问题描述:
最长上升子序列
给定一个长度为 N N N的数列 A A A,求数值单调递增的子序列最长是多少。 A A A的任意子序列 B B B可以表示为 B = { A k 1 , A k 2 , A k 3 , . . . , A k p } B=\{ A_{k_1},A_{k_2},A_{k_3},...,A_{k_p} \} B={Ak1,Ak2,Ak3,...,Akp},其中 k 1 < k 2 < k 3 < . . . < k p k_1<k_2<k_3<...<k_p k1<k2<k3<...<kp。
O ( n 2 ) O(n^2) O(n2)算法:
- 状态表示: f [ i ] f[i] f[i]表以 A [ i ] A[i] A[i]结尾的“最长上升子序列”的长度。
- 转移方程: f [ i ] = max 0 ≤ j < i , A [ j ] < A [ i ] { f [ j ] + 1 } f[i]=\max \limits_{0≤j<i,A[j]<A[i]}\{f[j]+1\} f[i]=0≤j<i,A[j]<A[i]max{f[j]+1}
此算法当 N ≤ 1000 N≤1000 N≤1000时不会TLE,但当 N > 1000 N>1000 N>1000时——
O ( n l o g n ) O(nlogn) O(nlogn)算法:
此时,表示的状态发生的改变,变为:
-
f
[
i
]
f[i]
f[i]表示长度为
i
i
i的上升子序列的最后一个数字的最小值。
读起来有点拗口
什么意思呢?
举个栗子:
设 A = { 2 , 1 , 4 , 7 , 9 , 5 , 6 } A=\{2,1,4,7,9,5,6\} A={2,1,4,7,9,5,6}, i i i表示从 A A A中选第 i i i个数作为子序列的结尾位置, t o p top top记录子序列的长度:
i = 0 i=0 i=0时,子序列结尾为 2 2 2
- 先
++top
,f[top]
即为长度为1的上升子序列,结尾为2 - 所以
f[1]=2
i = 1 i=1 i=1时 ,子序列结尾为 1 1 1
- 由于1<2,所以1不能加入{2}的末尾,即1只能单独当作一个上升子序列
- 此时,长度为1的上升子序列的结尾的最小值更新为1,即
f[1]=1
同理, i = 2 , 3 , 4 i=2,3,4 i=2,3,4时
++top
,4>1,所以4放入{1}的末尾,变为{1,4},子序列长度为2,末尾为4,即f[2]=4
++top
,7>4,放入{1,4}末尾为{1,4,7},长度为3,末尾为7,即f[3]=7
++top
,{1,4,7}变为{1,4,7,9},f[4]=9
i = 5 i=5 i=5时
- 看长度为4的上升子序列{1,4,7,9},5<7,9,不能放在7,9后,又5>4,所以5可以在4的后面
- 所以,长度为3的上升子序列的结尾更新为5,
f[3]=5
- 这一步是说,在
f
中找到≥A[i]
的最小值,将其替换成A[i]
… …
然而,还有一个问题,为什么要上升子序列的最后一个数字最小呢?
通俗的说,数越小,比它大的数就越多,也就是说,能放在它后面和它拼成上升子序列的数就越多
重点来了!!!
- 根据以上的描述,可以知道 f f f是一个单调递增的序列,说明其可以进行二分查找
- 二分查找的时间复杂度是 O ( l o g n ) O(logn) O(logn)
- 由此,只要用二分查出 A [ i ] A[i] A[i]在序列 f f f中的后继即可
- 最后,最长上升子序列的长度即为 t o p top top的值
- 时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
话不多说,上代码:
此处使用了C++STL库的upper_bound函数,具体用法看这儿—>传送门
#include<iostream>
#include<algorithm>
using namespace std;
int n,a,f[100005],top;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a;
if(a>=f[top]) f[++top]=a;
else f[upper_bound(f+1,f+1+top,a)-f]=a;
}
cout<<top<<endl;
return 0;
}