P10108 [GESP202312 六级] 闯关游戏题解

思路分析

关键观察:得分只发生在「离开第 \(s\) 关」的瞬间,拿的是 \(b_s\),与选哪个通道无关;通道只决定落到哪一关。所以每个状态只需要知道「我现在站在第 \(x\) 关」。

因为 \(a_i \ge 1\),每次前进的关卡号严格递增,不存在环 —— 这就是一个 DAG 上的最长路,按关卡号从小到大做 DP 即可。

状态定义:dp[x] = 站在第 \(x\) 关、且尚未领取 \(b_x\) 时,已累积的最大分数。不可达为 \(-\infty\)。

转移:在第 \(x\) 关选通道 \(i\),令 \(nx = x + a_i\),本步收益固定为 \(b_x\):

  • 若 \(nx \ge N\) → 直接通关,用 \(dp[x] + b_x\) 更新答案;
  • 否则 → dp[nx] = max(dp[nx], dp[x] + b[x])。

初始值 dp[0] = 0,答案为所有「通关时刻」的收益最大值。用 long long,因为最多走 \(10^4\) 步、每步 \(10^5\),总和可达 \(10^9\)(负分同理)。

复杂度 \(O(NM)\),即 \(10^4 \times 100 = 10^6\),非常宽松。

C++ 代码

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int N, M;
    cin >> N >> M;

    vector<int> a(M);
    for (int i = 0; i < M; i++) cin >> a[i];

    vector<long long> b(N);
    for (int i = 0; i < N; i++) cin >> b[i];

    const long long NEG = -(1LL << 60);   // 负无穷,避免溢出
    vector<long long> dp(N, NEG);
    dp[0] = 0;

    long long ans = NEG;
    for (int x = 0; x < N; x++) {
        if (dp[x] == NEG) continue;       // 该关不可达
        for (int i = 0; i < M; i++) {
            int nx = x + a[i];
            if (nx >= N) ans = max(ans, dp[x] + b[x]);   // 通关
            else         dp[nx] = max(dp[nx], dp[x] + b[x]);
        }
    }

    cout << ans << '\n';
    return 0;
}

几个容易踩的坑

  1. 通关那一步也要计分。离开第 \(x\) 关拿到 \(b_x\) 后才判断是否越界,样例里最后一关的 30 分正是这么来的,别漏掉。
  2. 负分关卡不能跳过。题目保证 \(|b_i|\) 可能为负,负分关卡如果必经就躲不开,所以初始化必须用真正的 \(-\infty\)(用 0 初始化会让「不可达」伪装成合法状态,导致答案偏大)。
  3. 正序遍历:依赖的是编号更小的状态,若写成逆序背包式循环会漏掉前面刚更新的值。

样例 1(\(N=6,\,a=\{2,3\},\,b=\{1,0,0,100,0,30\}\),输出 131)、全负分、\(N=1\) 边界三组数据验证通过。

posted @ 2026-10-21 12:50  kkman2000  阅读(7)  评论(0)    收藏  举报