LCIS 最长公共上升子序列学习笔记
什么是 LCIS
LCIS 是建立在 LIS 与 LCS 的基础之上的问题。需要我们求解不同序列的公共子序列中的最长上升子序列等问题。
例子
假设我们拥有以下两个序列:
a : 2 \ 2 \ 1 \ 3 \\ b : 2 \ 1 \ 2 \ 3那么这两个序列的最长公共上升子序列就是 ,长度为
解决方法
状态设计
习惯性写下了状态的表示:
dp_{i, j} \text {表示} a_{1 \sim i} \text {与} b_{1 \sim j} \text {的 LCIS 的长度}初始化
由于当子序列长度为 时并不存在 LCIS,所以进行如下初始化:
转移
- 当 时,由于我们外层循环为 ,所以此时
- 当 时,我们需要选出一个 且 使得 最大,则此时这个最大值为
综上,转移如下:
此时就可以使用三重循环来计算
for (int i = 2; i <= n; ++i) {
for (int j = 2; j <= n; ++j) {
if (a[i] != b[j]) {
dp[i][j] = dp[i - 1][j];
} else {
int maxVal = 0;
for (int k = 2; k < j; ++k) {
if (b[k] < a[i]) {
maxVal = max(maxVal, dp[i - 1][k]);
}
}
dp[i][j] = maxVal + 1;
}
}
}但是这样的时间复杂度为 ,能不能对其进行优化呢?
我们发现当计算到 时需要使用第三层循环来寻找 ,那么有没有一种方法能够快速寻找 。
基于以上想法,我们拥有两种常见思路:
- 构建 ST 表来做 RMQ,但对于每一个 都需要重新构建一次,复杂度为 ,比原先的暴力循环更慢
- 滑动窗口(单调队列)求最大值,同样对于每一个 都需要重新计算区间最值,同时区间长度不定,与暴力循环无区别
以上常见的方法都不行,那我们从转移本身进行思考,对于每一对 ,当内层对 循环时外层的 保持不变,则我们在循环 时记录一个变量 maxVal 表示 ,当 增加 时对新的 进行判断是否需要更新 maxVal 即可。时间复杂度为 。
优化后的代码
int maxVal = 0;
for (int i = 2; i <= n; ++i) {
maxVal = 0;
if (a[i] > b[1]) {
maxVal = dp[i - 1][1];
}
for (int j = 2; j <= n; ++j) {
if (a[i] == b[j]) {
dp[i][j] = maxVal + 1;
} else {
dp[i][j] = dp[i - 1][j];
}
if (a[i] > b[j]) {
maxVal = max(maxVal, dp[i - 1][j]);
}
}
}时间复杂度
综上,整体时间复杂度为 ,1s 内 的最大值约为
类似练习
- AcWing 272. 最长公共上升子序列