以 B 序列每个位置作为结尾维护 LCIS 长度与方案,扫描 A 序列时复用当前最优前驱,O(n²) 完成并直接输出序列。
OJ: roj
题目 ID: 1306
难度:普及+/提高-
标签:DP最长公共子序列最长上升子序列
创建: 2026-09-30 04:07
更新: 2026-10-07 11:01
形式化题目
给定两个整数序列
是 的子序列; 是 的子序列; 严格递增。
输出
正解
思路
设
- 若
,则 可以作为 的前驱,更新当前最优前驱; - 若
,则可以把 接在当前最优前驱后面,得到以 结尾的更长 LCIS。
为了直接输出方案,额外维护以每个
这样把朴素
代码
Python 版:
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-30 04:12
# update_at: 2026-09-30 04:12
import sys
def solve() -> None:
data = list(map(int, sys.stdin.buffer.read().split()))
it = iter(data)
n = next(it)
a = [next(it) for _ in range(n)]
m = next(it)
b = [next(it) for _ in range(m)]
# order[j] 记录以 a[j] 结尾的 LCIS:长度 + 序列本身。
lens = [0] * n
seqs: list[list[int]] = [[] for _ in range(n)]
for target in b:
cur_len = 0
cur_seq: list[int] = []
for j, x in enumerate(a):
if x < target and lens[j] > cur_len:
cur_len = lens[j]
cur_seq = seqs[j]
elif x == target:
lens[j] = cur_len + 1
seqs[j] = cur_seq + [x]
best = max(range(n), key=lambda j: lens[j], default=-1)
length = lens[best] if best != -1 else 0
print(length)
print(' '.join(map(str, seqs[best])) if best != -1 else '')
if __name__ == "__main__":
solve()C++ 版(同一算法):
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-04 23:58
* update_at: 2026-10-04 23:58
*/
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 505;
ll a[MAXN]; // 第一个序列 A
ll b[MAXN]; // 第二个序列 B
ll lens[MAXN]; // lens[i] 表示以 A[i] 结尾的最长上升公共子序列长度
ll pre[MAXN]; // pre[i] 记录 A[i] 在答案中的前驱下标,0 表示没有前驱
ll ans[MAXN]; // 还原出来的最长上升公共子序列
int main() {
int n = 0, m = 0;
if (scanf("%d", &n) != 1) {
return 0;
}
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
scanf("%d", &m);
for (int t = 1; t <= m; t++) {
scanf("%lld", &b[t]);
}
// 逐个取出 B 中的元素作为当前要接的末尾,扫描 A 更新状态。
for (int t = 1; t <= m; t++) {
ll best_len = 0; // 当前 target 之前能接上的最长上升公共子序列长度
int best_idx = 0; // 这个最长子序列在 A 中的结尾下标
for (int i = 1; i <= n; i++) {
if (a[i] < b[t] && lens[i] > best_len) {
// A[i] 比当前末尾小,可以作为前驱
best_len = lens[i];
best_idx = i;
} else if (a[i] == b[t]) {
// A[i] 可以作为当前公共子序列的末尾
lens[i] = best_len + 1;
pre[i] = best_idx;
}
}
}
// 在所有以 A[i] 结尾的答案中找最长的
int best = 0;
for (int i = 1; i <= n; i++) {
if (lens[i] > lens[best]) {
best = i;
}
}
ll length = lens[best];
// 顺着 pre 指针从后往前填回答案
int cnt = 0;
int cur = best;
while (cur != 0) {
ans[cnt] = a[cur];
cnt++;
cur = pre[cur];
}
reverse(ans, ans + cnt);
printf("%lld\n", length);
for (int i = 0; i < cnt; i++) {
if (i > 0) {
printf(" ");
}
printf("%lld", ans[i]);
}
printf("\n");
return 0;
}复杂度
- 时间复杂度:
, 。 - 空间复杂度:
,其中 为 LCIS 长度,用于保存每个位置结尾的序列。
总结
本题是 LCS 与 LIS 的结合。关键观察是把“以