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\) 。这样做有三个原因:
- \(dp_j<a_i, j\in [1,k-1]\) 他们能够把 \(dp_j\) 换成 \(a_i\) 这很显然,但是这样不够优。
- \(dp_j>a_i, j\in [k+1,len]\) 他们不一定能够换成 \(a_i\) ,能换的条件是什么,是我可能能从之前的一个状态推到这个状态,如果我把 \(dp_j\) 换成了 \(a_i\) 那么那个 \(dp_k\) 后面还接了个小于 \(dp_k\) 的?这肯定不合理。
- \(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]);
}
}