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;
}
几个容易踩的坑
- 通关那一步也要计分。离开第 \(x\) 关拿到 \(b_x\) 后才判断是否越界,样例里最后一关的 30 分正是这么来的,别漏掉。
- 负分关卡不能跳过。题目保证 \(|b_i|\) 可能为负,负分关卡如果必经就躲不开,所以初始化必须用真正的 \(-\infty\)(用
0初始化会让「不可达」伪装成合法状态,导致答案偏大)。 - 正序遍历:依赖的是编号更小的状态,若写成逆序背包式循环会漏掉前面刚更新的值。
样例 1(\(N=6,\,a=\{2,3\},\,b=\{1,0,0,100,0,30\}\),输出 131)、全负分、\(N=1\) 边界三组数据验证通过。

浙公网安备 33010602011771号