LIS:最长上升子序列
dongzirui0817 · · 算法·理论
Update on 2026.9.13:新增了 5 道例题。
本来想加 [ABC360G] Suitable Edit for LIS 的,但是这题似乎没有只用 LIS 就能解决的方案,题解区里唯一只用 LIS 的题解还被 hack 了。算鸟不加了,给感兴趣的读者食用。
LIS,即最长上升子序列。相信你们都会
LIS 例题
注意下面的做法是严格上升。
我们来考虑一个状态
显然越小答案越优,所以这是没问题的。我们考虑现在的最长上升子序列长为
- 如果
f_m<A_k ,那么f_{m+1} \gets A_k ,然后m \gets m + 1 。 - 如果
f_m\ge A_k ,那么我们找到最小的满足f_j\ge A_k 的位置,则:-
-
-
- 所以我们就二分找到这个
j ,然后f_j \gets A_k 即可。
-
使用 lower_bound 可更简洁地找到这个
int n, cnt = 0;
int f[100010];
int main() {
scanf("%d", &n);
for (int i = 1, x ; i <= n ; i++) {
scanf("%d", &x);
if (x > f[cnt]) f[++cnt] = x;
else f[lower_bound(f + 1, f + cnt + 1, x) - f] = x;
}
printf("%d\n", cnt);
return 0;
}
:::warning[注意]{open}
当
现在,我们把它运用在题目当中。
递增
贪心。显然挑出一个 LIS,把剩下的修改就一定可以满足。证明:无需证明。
所以答案就是
金币收集
由于从左往右走,所以考虑按地点排序。直接考虑有点困难,但注意到小 A 站原地越久越容易丢失越多的金币,所以考虑设
显然
该结论不必多说,只是不好发现。
注意:
- 求最长不降子序列时,要用的是
upper_bound。证明可以像 LIS 来证。 - 注意
p_i<0 的情况。
:::success[Code]
#include <bits/stdc++.h>
using namespace std;
int n;
pair <int, int> c[100010];
int f[100010], cnt = 0;
int main() {
scanf("%d", &n);
for (int i = 1 ; i <= n ; i++)
scanf("%d%d", &c[i].first, &c[i].second);
sort(c + 1, c + n + 1);
for (int i = 1 ; i <= n ; i++) {
int x = c[i].second - c[i].first;
if (x < 0) continue;
if (f[cnt] <= x)
f[++cnt] = x;
else f[upper_bound(f + 1, f + cnt + 1, x) - f] = x;
}
printf("%d\n", cnt);
return 0;
}
:::
[ABC134E] Sequence Decomposing
显然这题要求最少的上升子序列,使所有数都被覆盖。
结论:答案为最长不升子序列长度。
这也就是并非大名鼎鼎的 Dilworth 定理:将一个序列剖成若干个单调上升子序列的最少个数等于该序列最长不升子序列的长度。
:::info[有趣的事情]{open}
- 将一个序列剖成若干个上升子序列的最少个数等于该序列最长不升子序列的长度。
- 将一个序列剖成若干个不升子序列的最少个数等于该序列最长上升子序列的长度。
- 将一个序列剖成若干个下降子序列的最少个数等于该序列最长不降子序列的长度。
-
将一个序列剖成若干个不降子序列的最少个数等于该序列最长下降子序列的长度。
证明考虑贪心。设
显然这样构造一定是最优的,可以直接二分求解。其实这个过程就是在求最长不升子序列(当然没意识到也无所谓),于是做完了。
小 trick:求降序数组的大于 upper_bound(f + 1, f + cnt + 1, x, greater <int> ()) 实现。注意初始化
:::success[Code]
#include <bits/stdc++.h>
using namespace std;
int n;
int f[100010], cnt = 0;
int main() {
scanf("%d", &n);
memset(f, 127, sizeof(f));
for (int i = 1, x ; i <= n ; i++) {
scanf("%d", &x);
if (f[cnt] >= x)
f[++cnt] = x;
else f[upper_bound(f + 1, f + cnt + 1, x, greater <int> ()) - f] = x;
}
printf("%d\n", cnt);
return 0;
}
:::
导弹拦截
结论:第一问为最长不升子序列;第二问为最少不升子序列数量,也就是最长上升子序列。
这和上面的那题不一样的吗?双倍经验。
[ABC393F] Prefix LIS Query
回忆一下 LIS 的求法。可以发现,当前
所以如果
发现查询之间互相独立,考虑把它们离线下来按
:::success[Code]
#include <bits/stdc++.h>
using namespace std;
int n, q, cnt = 0;
int a[200010], c[200010], ans[200010];
vector <pair <int, int>> qu[200010];
int main() {
scanf("%d%d", &n, &q);
for (int i = 1 ; i <= n ; i++) scanf("%d", &a[i]);
for (int r, x, i = 1 ; i <= q ; i++)
scanf("%d%d", &r, &x), qu[r].push_back({x, i});
for (int i = 1 ; i <= n ; i++) {
if (a[i] > c[cnt]) c[++cnt] = a[i];
else c[lower_bound(c + 1, c + cnt + 1, a[i]) - c] = a[i];
for (auto y : qu[i])
ans[y.second] = upper_bound(c + 1, c + cnt + 1, y.first) - c - 1;
}
for (int i = 1 ; i <= q ; i++)
printf("%d\n", ans[i]);
return 0;
}
:::
[ABC354F] Useless for LIS
我们希望求出以下值:
此时只要有