最从公共上升子序列
最长公共上升子序列
题目描述
给定长度分别为 、 的两个整数序列 、,求它们的最长公共上升子序列的长度。
选出的序列需要同时满足:
- 是两个序列的公共子序列:可以不连续,但相对顺序不变。
- 元素严格递增:后一个数必须大于前一个数。
若没有公共元素,输出 。
输入格式
第一行两个整数 ,接下来两行分别包含 个和 个整数,表示序列 、。
输出格式
输出一个整数,表示最长公共上升子序列的长度。
数据范围
,,元素可以重复。
样例输入
4 4
3 1 2 4
1 3 2 4
样例输出
3
公共上升子序列 1 2 4 的长度为 。
状态与转移
为了判断能否接上一个更大的数,需要固定结尾。
设 表示:从 的前 个数中选取,与 构成的、以 结尾的公共上升子序列的最长长度。不存在时记为 。
- 不选 :保留 。
- 选 :必须有 ,再接在一个结尾小于它的状态后面。前一个结尾位置 必须满足 且 。
因此,先令 ;若 ,再更新:
$$dp[i][j]=\max\left(dp[i][j],\ 1+\max_{\substack{k<j\\b[k]<b[j]}}dp[i-1][k]\right).$$其中,内层没有可选状态时最大值取 ,表示由当前元素单独组成长度为 的序列。
直接枚举 ,时间复杂度为 。
用前缀最大值优化
固定 ,从左到右枚举 ,用 mx 维护已经经过的位置中,满足 的 的最大值。
- 若 ,用 更新
mx。 - 若 ,用
mx + 1更新 。
例如,处理样例中的 时:
b: 1 3 2 4
上一行 dp: 1 1 2 0
扫描前三个位置,mx 依次为 ;遇到最后的 ,便得到 ,对应 1 2 4。
每行开始时令 mx = 0,所有 初始为 。答案是 ,因为最优序列不一定以 结尾。
参考代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3005;
int n, m, a[N], b[N], dp[N][N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= n; ++i) cin >> a[i];
for (int j = 1; j <= m; ++j) cin >> b[j];
for (int i = 1; i <= n; ++i) {
int mx = 0;
for (int j = 1; j <= m; ++j) {
dp[i][j] = dp[i - 1][j];
if (b[j] < a[i]) mx = max(mx, dp[i - 1][j]);
if (b[j] == a[i]) dp[i][j] = max(dp[i][j], mx + 1);
}
}
int ans = 0;
for (int j = 1; j <= m; ++j) ans = max(ans, dp[n][j]);
cout << ans << '\n';
return 0;
}
时间复杂度 ,空间复杂度 。