按峰顶拆分:以每个景点为峰,正反各做一次 O(n^2) 的最长严格上升子序列 DP,ups[i]+downs[i]-1 的最大值即最多浏览景点数。
OJ: roj
题目 ID: 1283
难度:普及-
标签:DPLIS动态规划
创建: 2026-09-30 03:15
更新: 2026-10-05 07:50
题目描述
按编号递增的顺序浏览
输入第一行为 8 与 186 186 150 200 160 130 197 220,输出 4。
思路
即求最长的「先严格上升、后严格下降」子序列。枚举峰顶 up[i] 为以 down[i] 为从
参考代码
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 03:15
# update_at: 2026-09-30 03:15
import sys
def strictly_rising_lengths(heights: list[int]) -> list[int]:
"""dp[i] = 以 heights[i] 结尾的最长「严格上升」子序列长度(经典 O(n^2) LIS)。"""
dp: list[int] = []
for i, h in enumerate(heights):
# 前面所有比自己矮的位置都能接到 h 后面;都没有就只能自己当起点,长度 1
dp.append(1 + max((dp[j] for j in range(i) if heights[j] < h), default=0))
return dp
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
n = next(data) # 景点数,2 <= n <= 1000
heights = [next(data) for _ in range(n)] # 按游览顺序给出的海拔
up = strictly_rising_lengths(heights) # up[i]:从左端走到 i 且一路向上的最多景点数
down = strictly_rising_lengths(heights[::-1])[::-1] # down[i]:从 i 一路向下走到右端的最多景点数
# 反转序列后「向右下降」变成「向右上升」,同一份 DP 直接复用,结果再翻回原下标
# 峰顶 i 在 up、down 里各被数了一次,所以以 i 为峰能浏览 up[i] + down[i] - 1 个景点
print(max(u + d - 1 for u, d in zip(up, down)))
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-05 07:50
* update_at: 2026-10-05 07:50
*/
#include <iostream>
using namespace std;
typedef long long ll;
const int MAXN = 1005;
ll a[MAXN]; // 按游览顺序给出的景点海拔
ll up[MAXN]; // up[i]:以 i 结尾的最长严格上升子序列长度
ll down[MAXN]; // down[i]:从 i 开始向右的最长严格下降子序列长度
// 对序列 b[1..n] 求以每个位置结尾的最长严格上升子序列长度
void lis_strict(ll b[], ll dp[], ll n) {
for (ll i = 1; i <= n; i++) {
dp[i] = 1; // 至少包含自己
for (ll j = 1; j < i; j++) {
if (b[j] < b[i] && dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1;
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
ll n;
cin >> n;
for (ll i = 1; i <= n; i++) {
cin >> a[i];
}
lis_strict(a, up, n); // 正向:以 i 结尾的最长严格上升段
// 反转海拔后「向右严格下降」变成「向右严格上升」,复用同一份 LIS
ll rev[MAXN]; // 反转后的海拔
ll rev_dp[MAXN]; // 反转序列上每个位置结尾的最长严格上升长度
for (ll i = 1; i <= n; i++) {
rev[i] = a[n - i + 1];
}
lis_strict(rev, rev_dp, n);
for (ll i = 1; i <= n; i++) {
down[i] = rev_dp[n - i + 1];
}
ll ans = 0; // 最多能浏览的景点数
for (ll i = 1; i <= n; i++) {
ll total = up[i] + down[i] - 1; // 峰顶被两侧各数了一次,减 1
if (total > ans) {
ans = total;
}
}
cout << ans << "\n";
return 0;
}