1 条题解
-
0
最长公共子序列(LCS)问题题解
问题分析
本题要求计算两个序列的最长公共子序列(LCS)长度。子序列是指通过删除部分元素(不改变剩余元素相对顺序)得到的序列。
- 输入:两个序列的长度
n、m,以及两个序列a、b。 - 输出:LCS 的长度。
- 数据范围:
1 ≤ n, m, a_i, b_i ≤ 70000,传统的O(nm)动态规划会超时,需优化算法。
算法思路
传统的 LCS 动态规划(二维
dp[i][j])时间复杂度为O(nm),无法处理n, m达7e4的情况。需利用 序列映射 + 最长上升子序列(LIS) 优化,时间复杂度降至O((n + m) log min(n, m))。核心原理:
- 映射转化:若两个序列的 LCS 为
c_1, c_2, ..., c_k,则这些元素在序列a中的索引必为递增的(因子序列顺序不变)。因此,可将 LCS 问题转化为 LIS 问题。 - 具体步骤:
- 预处理序列
a,记录每个元素的所有索引(按出现顺序存储)。 - 将序列
b中的元素替换为其在a中的索引(仅保留在a中存在的元素),得到新序列c。 - 序列
c的 LIS 长度即为原问题的 LCS 长度(因 LIS 保证索引递增,对应a中元素顺序)。
- 预处理序列
实现细节
- 预处理索引:用哈希表存储
a中每个元素的索引列表(递增顺序)。 - 生成序列
c:对b中每个元素,若在a中存在,取其所有索引(反向遍历,确保优先选择大索引,优化 LIS 计算)。 - 计算 LIS:用贪心 + 二分查找维护
tails数组(tails[i]表示长度为i+1的 LIS 的最小末尾元素),高效计算 LIS 长度。
C++ 代码实现
#include <iostream> #include <vector> #include <unordered_map> #include <algorithm> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; vector<int> a(n), b(m); for (int i = 0; i < n; ++i) { cin >> a[i]; } for (int i = 0; i < m; ++i) { cin >> b[i]; } // 预处理a中每个元素的索引列表(递增) unordered_map<int, vector<int>> pos; for (int i = 0; i < n; ++i) { pos[a[i]].push_back(i); } vector<int> tails; // 维护LIS的末尾元素数组 for (int x : b) { if (pos.find(x) == pos.end()) { continue; // x不在a中,跳过 } // 反向遍历x在a中的索引列表(优先处理大索引,优化LIS) const auto& vec = pos[x]; for (auto it = vec.rbegin(); it != vec.rend(); ++it) { int v = *it; // 找tails中第一个 >= v的位置,替换或插入 auto idx = lower_bound(tails.begin(), tails.end(), v); if (idx == tails.end()) { tails.push_back(v); } else { *idx = v; } } } cout << tails.size() << endl; return 0; }复杂度分析
- 时间复杂度:
O(n + m * k_avg * log L),其中k_avg是a中元素的平均出现次数(总次数为n),L是tails数组长度(最大为min(n, m))。整体可简化为O((n + m) log min(n, m)),适用于7e4规模。 - 空间复杂度:
O(n + min(n, m)),用于存储索引列表和tails数组。
- 输入:两个序列的长度
- 1
信息
- ID
- 4890
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 21
- 已通过
- 1
- 上传者