专题:最长单调子序列(LIS)问题

专题:最长单调子序列(LIS)问题

(4.27 记录)

LIS问题是DP当中的一个很经典的问题。
LIS问题的核心在于状态的保护和转移。

一.LIS的长度
解法1:一般DP 时间复杂度O(n2)
很显然,对于某一个点i,1-i的最长单调子序列是唯一的,而以i结尾的最长单调子序列也是唯一的。这样就有思路:dp[i]表示以i结尾的最长单调子序列的长度。
这两种思路唯一的区别在于dp是否要转移,或者更直观的来说,区别在于dp[n]是否就是最长单调子序列的长度。
从A思路出发的话,1-i中的最长单调子序列长度一定不短于1-(i-1)的,所以初始情况下dp[i]=dp[i-1];从B思路出发的话,以i为结尾明显和以(i-1)结尾不是一回事,所以初始dp[i]=1.
有递推式:
dp[i] = max(dp[i],dp[j]+1) (i > j && a[i] > a[j])
注意:由于dp[n] ≠ max,需要枚举一遍dp找max。

解法2:队列伪模拟(或者说是贪心) 时间复杂度O(nlogn)
这个办法跟DP我觉得是一点儿关系都没有。
先给结论:维护一个单调的队列Q,记队尾为rear(初始为0),遍历a[i],如果a[i]<Q[rear]则入队,否则用a[i]替换掉Q中第一个不小于它的数。
然后我们解释一下:
现在假设我们维护的一个单调递增队列里面有三个数a,b,c,如果新增一个d,d>c,那么很显然这个队列就可以扩充。
如果d<c,那么我们明确一下:我们现在维护的这个队列并不一定是真实的LIS,对于那个队列abc,其实b可以替换成任意一个(a,c)中的数,其他的数也同理。但是如果我们改的是队尾,很显然队尾越小越好,这样我们更有可能扩充这个序列的长度(靠替换那是没法扩充的),为了队尾越小越好,那前面的肯定也是越小越好,尽管这不是真实的,但是如果队尾真的更新了,那么真实的LIS也就变了;如果没有那就算了,反正我们压根就不关心到底队列里面是什么,我们只关心长度。
大致如下(以单调递增为例):

if(Q[rear} < a[i] Q[++rear] = a[i];
else Q[find(a[i])] = a[i];

(这个find里面怎么写,一会儿会讨论一下)

二.LIS
现在我们关心序列是什么了。
从刚才已有的里面看一看:解法2不行,因为一个不真实的序列压根没用。
所以我们就剩一个解法1可以改一改了。
我们重复一遍解法1的核心:对于一个点i,以它为结尾的LIS唯一确定。
这样一来,我们借鉴一下链表思想就行了,用head[x]记录以x为结尾的最长单调子序列的倒数第二个数,我们一直找head[x]并输出,就能得到一个真实的LIS。这种思想跟拓扑排序非常类似,至于拓扑,上一个记录已经写过了。
由于这是一个LIS里面的重难点,给一个完整的板子:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int head[100001],a[100001];
void find(int x){
   
	if(~head[x]) 
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值