LIS(最长上升子序列)O(nlogn)优化

问题描述:

最长上升子序列

给定一个长度为 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]=0j<i,A[j]<A[i]max{f[j]+1}

此算法当 N ≤ 1000 N≤1000 N1000时不会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

  • ++topf[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;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值