LIS 的重探寻

LIS 的 \(\mathbf{O} (n\log n)\) 做法于我成为了历史遗留问题,近来终于研究明白。

感觉难度绝对不止黄。

这里说一下 LIS 的思路。

\(dp_i\) 表示现在的长度是 \(i\) 的上升子序列的第 \(i\) 项的 最小值\(len\) 表示到现在为止的最长上升子序列的长度。

所以,这里的 \(dp_i\) 并不一定是我取的序列的第 \(i\) 个东西。

分类讨论 \(dp_{len}<a_i\) 这样的话我们直接令 \(dp_{len+1}=a_i\) 能加到后面就直接加。

如果不能加,那么我们找到之前的一个位置 \(k\) 满足 \(dp_k>a_i\) 然后把他改为 \(a_i\) 。这样做有三个原因:

  1. \(dp_j<a_i, j\in [1,k-1]\) 他们能够把 \(dp_j\) 换成 \(a_i\) 这很显然,但是这样不够优。
  2. \(dp_j>a_i, j\in [k+1,len]\) 他们不一定能够换成 \(a_i\) ,能换的条件是什么,是我可能能从之前的一个状态推到这个状态,如果我把 \(dp_j\) 换成了 \(a_i\) 那么那个 \(dp_k\) 后面还接了个小于 \(dp_k\) 的?这肯定不合理。
  3. \(dp_k\) 是一个刚刚好好的值,它既可以从之前的推出,又可以替换现在不优的。

什么?你问为什么 \(dp\) 数组不是我们取的数列但是我们算出的 \(len\) 还是正确的?

很简单。

因为我们的 \(len\) 是不是已经尽量伸出去了,并且 \(a_i\) 已经通过上面的变化待在了最优的位置,那么不可能存在操作更优。

关键代码很简单,查询位置 \(k\) 满足 \(dp_k>a_i\) 的时候我们用 stl 自带的 upper_bound 就 ok 了。

int len=0;
for(int i=1;i<=m;++i){
	if(dp[len]<b[i]) dp[++len]=b[i];
	else{
		int t=lower_bound(dp+1,dp+len+1,b[i])-dp;
		dp[t]=min(dp[t],b[i]);
	}
}
posted @ 2022-07-07 19:36  Mercury_City  阅读(128)  评论(0编辑  收藏  举报