int n, m, a[MAXN], b[MAXM], f[MAXN][MAXM];int dp() { for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) if (a[i] == b[j]) f[i][j] = f[i - 1][j - 1] + 1; else f[i][j] = std::max(f[i - 1][j], f[i][j - 1]); return f[n][m];}
Python
def dp(n, m, a, b): f = [[0] * (m + 1) for _ in range(n + 1)] for i in range(1, n + 1): for j in range(1, m + 1): if a[i] == b[j]: f[i][j] = f[i - 1][j - 1] + 1 else: f[i][j] = max(f[i - 1][j], f[i][j - 1]) return f[n][m]
int n, a[MAXN], d[MAXN];int dp() { d[1] = 1; int ans = 1; for (int i = 2; i <= n; i++) { d[i] = 1; for (int j = 1; j < i; j++) if (a[j] <= a[i]) { d[i] = std::max(d[i], d[j] + 1); ans = std::max(ans, d[i]); } } return ans;}
Python
def dp(n, a): d = [0] * (n + 1) d[1] = 1 ans = 1 for i in range(2, n + 1): d[i] = 1 for j in range(1, i): if a[j] <= a[i]: d[i] = max(d[i], d[j] + 1) ans = max(ans, d[i]) return ans
容易发现该算法的时间复杂度为 O(n2)。
算法二
当 n 的范围扩大到 n≤105 时,第一种做法就不够快了,下面给出了一个 O(nlogn) 的做法。
考虑之前定义的状态 (i,l),表示序列以第 i 个元素结尾的不下降子序列最长为 l。不同于以往按固定 i 处理状态的方法,这里直接判断 (i,l) 是否合法:
解释:若直接插在末尾,会破坏 d 的单调性;替换操作可以保证每个长度的末尾元素尽可能小,从而为后续元素保留更多可能性。
优化:因为 d 单调不减,可用二分查找直接找到元素的插入位置,将整体复杂度降低到 O(nlogn) 而非暴力查找的 O(n2)。
如果还要输出具体的最长不下降子序列,可以额外维护数组 dx′,表示长度为 x 的不下降子序列中末尾最小元素的位置(有多个可任选一个)。具体维护时,只需要在插入元素 ai 到 dx 时,同时更新 dx′ 为 i 即可。同时,需要记录 i 的最优前驱 pi 为 dx−1′。最终,从任意最大长度状态出发,沿前驱 pi 回溯,即可得到完整子序列。
参考实现
[list2tab]
C++
int n, a[MAXN], d[MAXN], di[MAXN], pre[MAXN], res[MAXN];int dp() { int ans = 0; for (int i = 1; i <= n; ++i) { int tmp = std::upper_bound(d, d + ans, a[i]) - d; pre[i] = tmp ? di[tmp - 1] : -1; d[tmp] = a[i]; di[tmp] = i; if (tmp == ans) ++ans; } // Construct the subsequence. for (int k = ans, i = di[ans - 1]; k; --k) { res[k] = a[i]; i = pre[i]; } return ans;}
Python
n = 0a = [0] * MAXNd = [0] * MAXNdi = [0] * MAXNpre = [0] * MAXNres = [0] * MAXNdef dp(): ans = 0 for i in range(1, n + 1): tmp = bisect.bisect_right(d, a[i], 0, ans) pre[i] = di[tmp - 1] if tmp else -1 d[tmp] = a[i] di[tmp] = i if tmp == ans: ans += 1 # Construct the subsequence k = ans i = di[ans - 1] while k: res[k] = a[i] i = pre[i] k -= 1 return ans
该算法的时间复杂度为 O(nlogn)。输出答案的时间复杂度为 O(ans)。
注意
对于最长 上升 子序列问题,类似地,可以令 di 表示所有长度为 i 的最长上升子序列的末尾元素的最小值。
需要注意的是,在步骤 2 中,若 ai≤dlen,由于最长上升子序列中相邻元素不能相等,需要在 d 序列中找到 第一个不小于ai 的元素,用 ai 替换之。