【动态规划】登山模型-先上升后下降

题目传送门:AcWing 1014. 登山


一、题目重述

  • N 个景点,按顺序浏览(编号递增)。
  • 不能连续浏览海拔相同的两个景点。
  • 一旦开始下山(海拔递减),就不能再上升。
  • 求最多能浏览的景点数。

输入格式

  • 第一行:N(景点数量,2 ≤ N ≤ 1000)。
  • 第二行:N 个整数,表示每个景点的海拔。

输出格式

  • 一个整数,表示最多能浏览的景点数。

示例

输入:
8
186 186 150 200 160 130 197 220
输出:
4

二、题目分析

问题转化为:求一个 先严格上升、后严格下降 的子序列(不能有相邻相同海拔),且长度最长。


三、算法分析

  1. 关键点
    • 类似于“最长上升子序列”的变种。
    • 需要分别计算以每个点为峰值的 上升序列下降序列 的长度之和。
  2. 步骤
    • 从左到右求每个点的最长上升子序列长度(f[i])。
    • 从右到左求每个点的最长下降子序列长度(g[i])。
    • 结果为 max(f[i] + g[i] - 1)(减去重复计算的峰值点)。

四、动态规划思路

a. 状态表示
  • f[i]:以 a[i] 结尾的最长上升子序列长度。
  • g[i]:以 a[i] 开头的最长下降子序列长度。
b. 初始化
  • f[i] = 1(至少包含自己)。
  • g[i] = 1
c. 状态转移
  • 上升部分
    for (int i = 1; i <= n; i++) {
        f[i] = 1;
        for (int j = 1; j < i; j++) {
            if (a[j] < a[i]) f[i] = max(f[i], f[j] + 1);
        }
    }
    
  • 下降部分
    for (int i = n; i >= 1; i--) {
        g[i] = 1;
        for (int j = n; j > i; j--) {
            if (a[j] < a[i]) g[i] = max(g[i], g[j] + 1);
        }
    }
    
d. 最终结果
  • res = max(f[i] + g[i] - 1)

五、代码实现

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1010;
int n, a[N], f[N], g[N];

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];

    // 正向求最长上升子序列
    for (int i = 1; i <= n; i++) {
        f[i] = 1;
        for (int j = 1; j < i; j++) {
            if (a[j] < a[i]) f[i] = max(f[i], f[j] + 1);
        }
    }

    // 反向求最长下降子序列
    for (int i = n; i >= 1; i--) {
        g[i] = 1;
        for (int j = n; j > i; j--) {
            if (a[j] < a[i]) g[i] = max(g[i], g[j] + 1);
        }
    }

    // 合并结果
    int res = 0;
    for (int i = 1; i <= n; i++) res = max(res, f[i] + g[i] - 1);
    cout << res << endl;
    return 0;
}

六、重点细节

  1. 海拔相同:题目要求不能连续相同,但代码中 a[j] < a[i] 已经隐含处理(严格递增/递减)。
  2. 反向遍历:计算 g[i] 时需从右向左,表示“从 i 开始下降”。
  3. 峰值重复f[i] + g[i] - 1 是因为峰值点被计算了两次。

七、复杂度分析

  • 时间复杂度:O(),两重循环。
  • 空间复杂度:O(N),存储 fg 数组。

八、总结

  • 本题是 最长上升子序列 的变形,通过正反两次动态规划求解。
  • 关键点在于理解“先上升后下降”的模型,并合并两部分结果。
  • 类似题目:AcWing 482. 合唱队形(几乎相同)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值