- 答疑
最长递增子序列
- 2025-8-28 21:31:58 @
题意概括
给定一个长度为 的数字序列 ,找到其一个子序列 ,使得该子序列是严格单调递增的(即 ),并且其长度 是所有可能的递增子序列中最长的。输出这个最长的长度。
数据范围:
- 序列长度 满足 。
- 序列中的每个元素 满足 。
基础知识
最长递增子序列 (Longest Increasing Subsequence, LIS)
解决 LIS 问题通常有两种方法:动态规划和贪心 + 二分查找。
-
动态规划 (DP) -
- 定义状态: 表示以第 个元素 结尾的最长递增子序列的长度。
- 状态转移方程:$dp[i] = \max_{0 \le j < i, a_j < a_i} \{dp[j]\} + 1$。
- 遍历所有 ,如果 ,说明 可以接在以 结尾的递增子序列后面,形成一个更长的子序列。我们取所有这些可能情况中的最大长度加一。
- 最终答案为 。
- 这种方法对于 的数据范围会超时。
-
贪心 + 二分查找 -
- 这是解决本题的标准高效算法。
- 我们维护一个数组
d
,其中d[k]
存储的是所有长度为 的递增子序列中,结尾元素的最小值。这个d
数组一定是单调递增的。 - 遍历原始序列 的每一个元素 :
- 如果 大于
d
数组中所有元素(即大于d
的最后一个元素),说明 可以接在目前最长的递增子序列后面,形成一个更长的递增子序列。我们将 直接添加到d
数组的末尾,LIS 的长度加一。 - 如果 不大于
d
的最后一个元素,我们就在d
数组中找到第一个大于或等于 的元素d[k]
,并用 替换它。这表示我们找到了一个长度为 的、但结尾更小(为 )的递增子序列。这个更小的结尾元素为后续元素提供了更大的增长潜力。
- 如果 大于
- 由于
d
数组是单调的,查找过程可以用二分查找来完成,时间复杂度为 。 - 遍历完整个序列 后,
d
数组的长度就是原序列 LIS 的长度。
C++ 代码实现
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 5;
int n;
int a[N];
vector<int> d; // d[k] 存储长度为 k+1 的 LIS 的最小结尾元素
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
if (n == 0) {
cout << 0 << endl;
return 0;
}
d.push_back(a[0]); // 初始化,LIS 长度为 1
for (int i = 1; i < n; i++) {
int x = a[i];
if (x > d.back()) {
// 如果 x 比所有已知 LIS 的结尾都大,它可以扩展最长的 LIS
d.push_back(x);
} else {
// 否则,找到第一个大于等于 x 的位置,用 x 替换它
// 这会得到一个结尾更小的同样长度的 LIS,更有潜力
auto it = lower_bound(d.begin(), d.end(), x);
*it = x;
}
}
cout << d.size() << endl;
return 0;
}
0 条评论
目前还没有评论...