核心概述

最长上升子序列(LIS):在序列中寻找严格递增的最长子序列,可在 O(n log n) 下求长度并按需重构方案,且可切换为非降等常见变体。

原文引述

Description: Compute LIS in O(n log n) via patience sorting (tails) with binary search; supports reconstruction and non-decreasing variant by switching lower/upper_bound.(摘自本节点现有英文注释) Time: O(n log n)(摘自本节点现有英文注释) Status: tested(摘自本节点现有英文注释)

展开阐述

  • 定义与变体

    • 严格上升:a[i1] < a[i2] < … < a[ik](经典 LIS)
    • 非降序列:a[i1] ≤ a[i2] ≤ …(将 lower_bound 改为 upper_bound)
    • 重构方案:用前驱数组记录每个位置的连接来源,以回溯一条合法的 LIS
    • 值域处理:当值域很大或需要等值细化策略时可进行离线坐标压缩
  • 算法一:O(n^2) DP

    • 状态:dp[i] 表示以 i 结尾的 LIS 长度
    • 转移:dp[i] = 1 + max{ dp[j] | j < i 且 a[j] < a[i] }
    • 答案:max_i dp[i]
    • 复杂度:O(n^2),实现简单,适合中小规模
  • 算法二:O(n log n) 二分(“扑克牌接龙”/patience sorting)

    • 维护 tails,其中 tails[len-1] 记录“长度为 len 的上升子序列的最小可能结尾值”
    • 对每个 a[i]:
      • it = lower_bound(tails.begin(), tails.end(), a[i])(严格上升)
      • it == tails.end() 则追加,否则覆盖 *it
    • 若需非降,将 lower_bound 改为 upper_bound
    • 可同时维护 pos[len-1] 为尾元素下标,以及 prev[i] 前驱下标以支持重构
  • 代码框架(重构严格 LIS)

// 返回 LIS 长度,并在 seq 中放入一组 LIS(按原序重构)
int lis(const vector<int>& a, vector<int>& seq) {
    int n = (int)a.size();
    vector<int> tails;          // 仅存值,用于长度
    vector<int> pos;            // 每个长度对应的尾元素下标
    vector<int> prev(n, -1);    // 前驱下标,用于重构
 
    tails.reserve(n);
    pos.reserve(n);
 
    for (int i = 0; i < n; ++i) {
        auto it = lower_bound(tails.begin(), tails.end(), a[i]); // 严格上升
        int j = int(it - tails.begin()); // j 为新长度-1
        if (it == tails.end()) {
            tails.push_back(a[i]);
            pos.push_back(i);
        } else {
            *it = a[i];
            pos[j] = i;
        }
        if (j > 0) prev[i] = pos[j-1];
    }
    // 重构
    seq.clear();
    for (int p = pos.back(); p != -1; p = prev[p]) seq.push_back(a[p]);
    reverse(seq.begin(), seq.end());
    return (int)tails.size();
}
  • 复杂度与边界

    • O(n log n) 求长度,空间 O(n);重构为线性时间追加
    • 严格/非降的等值处理需与 lower/upper_bound 的选择一致
    • 二维点集等变体常用“一维排序,另一维做 LIS”,相等时排序方向需与“严格/非降”保持一致
  • 正确性要点

    • 不变式:tails 维护“同长度最优结尾值”,更小的结尾更利于后续扩展
    • 结构保真:通过 pos/prev 保留路径信息,不影响长度计算的正确性

关联节点