LIS 与 LCS
最长上升子序列(LIS)
给定长度为 \(n(1 \leqslant n \leqslant 10^5)\) 的数组 \(a\),求出 \(a\) 的最长上升子序列。
暴力
- 令 \(dp_i\) 表示以 \(a_i\) 结尾的最长上升子序列。
- 我们显然可以得到如下转移: \(dp_{i} = \max\limits_{1 \leqslant j < i, a_j < a_i}\{dp_j\} + 1\),若不存在满足要求的 \(j\),\(dp_i = 1\)。
时间复杂度
状态数:\(O(n)\),转移数:\(O(n^2)\),总时间复杂度:\(O(n ^ 2)\)。
二分优化
- 令 \(f_i\) 表示一个长度为 \(i\) 上升子序列末尾元素的最小值。
- 通过贪心可以发现 \(f_i < f_{i + 1}\),因为假设某个长度为 \(i\) 且末尾元素为 \(f_i\) 的上升子序列倒数第二个数为 \(x\),那么显然存在 \(x < f_i\),而肯定这个上升子序列的前 \(i - 1\) 个构成一个长度 \(i - 1\) 的上升子序列,那么就有 \(f_{i - 1} \leqslant x < f_i\)。
- 并且对于每个 \(i\),\(f_i\) 都越小越好,以便给后面的元素留更大的空间,所以最开始 \(f_0 = -\infty, f_i = \infty(i > 1)\)。
- 而且对于每个元素 \(x\),能把它作为一个长度为 \(i\) 的上升子序列末尾,一定不会让它只做长度为 \(i - 1\) 的上升子序列末尾,否则就算把它作为长度为 \(i - 1\) 的上升子序列末尾,在它后面填一个元素得到的 \(f_i\) 一定大于 \(x\),违背了第二条。
- 知到了上面三条后,我们可以依次枚举每个元素 \(a_i\), 然后通过二分找到第一个的 \(j\) 使得 \(f_j \geqslant a_i\)。如果现在的最长上升子序列长度为 \(j - 1\),最长上升子序列长度 \(+1\)。然后 \(f_{j} = a_i\)。
时间复杂度
-
枚举每个元素:\(O(n)\)。
-
每个元素进行一次二分:\(O(n \log n)\)。
-
总时间复杂度:\(O(n \log n)\)。
Code
int n, m, a[MAXN], f[MAXN];
for (int i = 1; i <= n; i++) {
int k = lower_bound(f + 1, f + m + 1, a[i]) - f; 找到第一个 k 使得 f[k] >= a[i]
if (k > m) {
f[++m] = a[i];
} else {
f[k] = a[i];
}
}
线段树 / 树状数组优化
我们可以转换一下式子,令 \(dp_i\) 表示以 \(i\) 为结尾的最长上升子序列长度,依次枚举每个元素 \(x\) 时,只有 \(dp_x\) 会更新为 \(\max(dp_x, \max\limits_{i = 0}^{x - 1} \{dp_i + 1\})\),我们发现发现后面那一坨可以用线段树 / 树状数组优化,并且这里只有单点修改和区间查询。
时间复杂度
- 枚举每个元素:\(O(n)\)。
- 用线段树或树状数组进行查询、修改:\(O(\log n)\)。
- 总时间复杂度:\(O(n \log n)\)。
最长公共子序列(LCS)
- 求一个长度为 \(n\) 的数组 \(a\) 和一个长度为 \(m\) 的数组 \(b\),求 \(a, b\) 最长公共子序列(即找到最长一个数组的 \(s\) 使得 \(s\) 既为 \(a\) 的子序列又为 \(b\) 的子序列)。\((1 \leqslant n, m \leqslant 1000)\)
- 令 \(dp_{i, j}\) 表示 \(a\) 的前 \(i\) 个元素和 \(b\) 的前 \(j\) 个元素的最长公共子序列。
- \(dp_{0, i} = dp_{i, 0} = 0\),\(dp_{i, j} = \begin{cases} dp_{i - 1, j - 1} + 1(a_i = b_j) \\ max(dp_{i - 1, j}, dp_{i, j - 1})(a_{i} \ne a_j) \end{cases}\)。第一种转移就是将 \(a_i, b_j\) 接到公共子序列后面,第二种就是不能接的普通转移。
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (s[i] == t[j]) { // 第一种情况
dp[i][j] = dp[i - 1][j - 1] + 1;
} else { // 第二种情况
dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]);
}
}
}
时间复杂度
状态数:\(O(nm)\),转移数:\(O(nm)\),总时间复杂度:\(O(nm)\)。
浙公网安备 33010602011771号