按结尾定义长度 + 用值到最后出现位置的桶替代前驱枚举 + 沿 pre 链回推下标
OJ: codeforces
题目 ID: 977F
难度:普及
形式化题目
给定序列
正解
思路
以
记 last[v] 记值 pre 数组记前驱下标,从
样例 2 3 5 6。
桶若改成「值第一次出现的位置」,
代码
C++ 用 unordered_map 存桶,Python 用 defaultdict 让缺键直接拿到
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-11-04 14:50
* update_at: 2026-11-04 19:08
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 2e5 + 5;
int n;
ll a[MAXN]; // 原序列,下标从 1 开始
int f[MAXN]; // f[i]:以 i 结尾的最长连续递增子序列长度,最大为 n,int 够用
int pre[MAXN]; // pre[i]:拼出 f[i] 时 i 前面接的下标,-1 表示没有前驱
unordered_map<ll, int> last; // last[v]:值 v 最后一次出现的下标;键来自输入,不必防构造攻击
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
last.reserve(n * 2); // 键最多 n 个,先预留桶,避免反复 rehash
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
// 前驱的值只能是 a[i] - 1,所以查一次表就够,不用回扫
for (int i = 1; i <= n; ++i) {
f[i] = 1;
pre[i] = -1;
auto it = last.find(a[i] - 1);
if (it != last.end()) {
f[i] = f[it->second] + 1;
pre[i] = it->second;
}
last[a[i]] = i; // 每次出现都覆盖,桶里留的是最后一次出现
}
int ans_pos = 1; // 取第一个最大长度的结尾,题面允许任意一组合法答案
for (int i = 2; i <= n; ++i) {
if (f[i] > f[ans_pos]) {
ans_pos = i;
}
}
cout << f[ans_pos] << "\n";
// 沿 pre 回退得到倒序的下标序列,反转后输出
vector<int> path;
for (int pos = ans_pos; pos != -1; pos = pre[pos]) {
path.push_back(pos);
}
reverse(path.begin(), path.end());
for (int pos : path) {
cout << pos << " ";
}
cout << "\n";
return 0;
}python
# 直接对每个位置往前扫前驱就是 O(n^2),会超时。
# 记 f[i] 为以 i 结尾的最长连续递增子序列长度。
# 前驱的值必然是 a[i] - 1,所以只需要记住「值 v 最后一次出现的下标」。
import sys
from collections import defaultdict
def solve(data):
n = next(data)
a = [next(data) for _ in range(n)]
last = defaultdict(lambda: -1) # last[v] = 值 v 最后一次出现的下标
pre = [-1] * n # pre[i] = 以 i 结尾时的前一个位置
f = [1] * n # f[i] = 以 i 结尾的最长连续递增子序列长度
for i in range(n):
j = last[a[i] - 1] # 值 a[i] - 1 最后一次出现的位置
if j != -1:
f[i] = f[j] + 1
pre[i] = j
last[a[i]] = i
ans = max(f)
print(ans)
pos = f.index(ans) # 任取一个取到最大长度的结尾
output = []
while pos != -1:
output.append(pos)
pos = pre[pos]
print(*[p + 1 for p in output[::-1]])
if __name__ == "__main__":
data = iter(map(int, sys.stdin.buffer.read().split()))
solve(data)复杂度
时间期望 pre 各
总结
- 前驱的值由
唯一确定,把不确定的「位置」换成「值 → 最后一次出现」, 的枚举就成了一次查表。 - 需要输出方案时加一个
pre数组沿链回退,相邻两项差为定值时把 换成 即可。 - 验证:四组样例 C++/Python 均通过;与
暴力对拍 组、两版互拍 组无差异; 链式数据 C++ ms / MiB,Python ms / MiB。

