📘 线性DP:最长上升子序列(LIS)

2026-08-25
⭐⭐ GESP 7级

📖 概念讲解

LIS(Longest Increasing Subsequence)是线性DP的经典题型。给定一个序列,找出其中严格递增的最长子序列长度。注意:子序列不要求连续,但要保持相对顺序。

核心思路:dp[i] = 以第 i 个元素结尾的最长上升子序列长度。转移方程:

💡 状态转移:dp[i] = max(dp[j] + 1),其中 0 ≤ j < i 且 a[j] < a[i]。即在 i 之前找所有比 a[i] 小的元素,取它们 dp 值的最大值加 1。
💡 初始化:每个元素自身至少构成长度为 1 的子序列,所以 dp[i] = 1。

时间复杂度 O(n²),n ≤ 1000 时可直接用。更优的 O(n log n) 解法可用二分+贪心优化。

💻 代码示例

1#include <iostream>
2#include <algorithm>
3using namespace std;
4
5int main() {
6 int n; cin >> n; // 读入序列长度
7 int a[1010], dp[1010]; // a存序列,dp[i]表示以a[i]结尾的LIS长度
8 for (int i = 0; i < n; i++)
9 cin >> a[i]; // 读入序列每个元素
10
11 for (int i = 0; i < n; i++)
12 dp[i] = 1; // 每个元素自身就是长度1的子序列
13
14 for (int i = 1; i < n; i++) // 从第2个元素开始枚举
15 for (int j = 0; j < i; j++) // 遍历i之前的所有元素
16 if (a[j] < a[i]) // 如果a[j]比a[i]小,可以接在后面
17 dp[i] = max(dp[i], dp[j] + 1); // 状态转移:取最大值
18
19 int ans = 0;
20 for (int i = 0; i < n; i++)
21 ans = max(ans, dp[i]); // 答案是所有dp[i]中的最大值
22 cout << ans << endl;
23 // 输入:6 3 1 4 1 5 9
24 // 输出:4 (子序列 1 4 5 9)
25}

🧩 互动小测

题目 1:序列 [2, 1, 3, 4, 5] 的 LIS 长度是?

题目 2:dp[i] 的含义是什么?

题目 3:如果要找最长非递减子序列,转移条件应该怎么改?

🏋️ 动手练一练

📝 编程练习

题目:导弹拦截(简化版)
某国研发了一套导弹拦截系统,第一套系统每次拦截的导弹高度必须严格递减(即后一枚的高度 < 前一枚的高度)。给定 n 枚来袭导弹的高度序列,求这套系统最多能拦截多少枚导弹。

输入:第一行 n(1 ≤ n ≤ 1000),第二行 n 个正整数表示高度。
输出:最多拦截的导弹数。
提示:导弹拦截要求"严格递减",这和 LIS 的"严格递增"是对称的——只需要把判断条件反过来就行。
参考答案:
// 和 LIS 完全对称,只是判断条件反过来
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
    int n; cin >> n;
    int a[1010], dp[1010];
    for (int i = 0; i < n; i++) cin >> a[i];
    for (int i = 0; i < n; i++) dp[i] = 1;  // 初始化
    for (int i = 1; i < n; i++)
        for (int j = 0; j < i; j++)
            if (a[j] > a[i])               // 关键:严格递减
                dp[i] = max(dp[i], dp[j] + 1);
    int ans = 0;
    for (int i = 0; i < n; i++) ans = max(ans, dp[i]);
    cout << ans << endl;
    // 输入:6  5 3 4 2 1 6
    // 输出:4  (拦截序列 5 3 2 1)
}
        

要点:
  • 拦截系统要求严格递减,所以把 a[j] < a[i] 改成 a[j] > a[i]。
  • 这道题本质就是求最长下降子序列(LDS),和 LIS 完全对称。
  • 如果题目改为"非递增"(允许相等),就把 > 改成 >=。

📝 易错点提醒

学完这个知识点后点一下