记录95
cpp
#include<bits/stdc++.h>
using namespace std;
int a[20010],skip[110];//通道就是可跳的长度
long long dp[20010];//跳的最远长度
int main(){
int N,M;
cin>>N>>M;
int max_s=0;//最远长度
for(int i=0;i<M;i++){
cin>>skip[i];
max_s=max(max_s,skip[i]);
}//默认dp都是不连通的,即不可达
for(int i=0;i<N+max_s;i++) dp[i]=INT_MIN;
for(int i=0;i<N;i++) cin>>a[i];
for(int i=0;i<M;i++) dp[skip[i]]=a[skip[i]]+a[0];//更新可通初始点
dp[0]=a[0];//不忘第一个
for(int i=1;i<N+max_s;i++){
for(int j=0;j<M;j++){
if(i>=skip[j]&&dp[i-skip[j]]!=INT_MIN){
dp[i]=max(dp[i],a[i]+dp[i-skip[j]]);
}//从i点开始看是从前面哪里跳过来的
}//长度没超并且前面有可达点
}//a[i]有值就是几,没有就是0
long long ans=INT_MIN;
for(int i=N-1;i<N+max_s;i++) ans=max(ans,dp[i]);//终点与能跳最远距离中
cout<<ans;//最大的得分
//for(int i=0;i<N+max_s;i++) cout<<dp[i]<<' ';
return 0;
}
题目传送门
https://www.luogu.com.cn/problem/P10108
突破口
你来到了一个闯关游戏。
这个游戏总共有 N 关,每关都有 M 个通道,你需要选择一个通道并通往后续关卡。其中,第 i 个通道可以让你前进 ai 关,也就是说,如果你现在在第 x 关,那么选择第 i 个通道后,你将直接来到第 x+ai 关(特别地,如果 x+ai≥N,那么你就通关了)。此外,当你顺利离开第 s 关时,你还将获得 bs 分。
游戏开始时,你在第 0 关。请问,你通关时最多能获得多少总分。
思路
🎯 游戏规则
- 有 N 关(编号 0 到 N−1)
- 每关都有 M 个通道 ,每个通道让你 前进
a[i]关 - 当你 离开第 s 关 (即从第 s 关跳走),获得分数
b[s] - 起点:第 0 关(尚未获得 b[0])
- 通关条件:跳到 ≥ N 的位置
✅ 注意:只有当你"离开"某一关时才得分
所以如果你从第 x 关跳走 → 得
b[x]
🎯 目标
求 通关时能获得的最大总分
🧠 解题思路:动态规划(DP)
关键观察
- 这是一个 有向图上的最长路径问题
- 状态:
dp[i]= 到达第 i 关(并准备从此跳走)时能获得的最大总分 - 转移:从前面某个位置
i - skip[j]跳skip[j]步到达 i → 得b[i]
💡 但注意:你必须"离开"第 i 关才能得
b[i]所以
dp[i]应理解为:已经获得了 b[i],且当前位于第 i 关(即将跳走)
边界处理
- 起点在第 0 关,但还没跳 → 要先获得 b[0] 才能开始跳
- 所以初始状态:可以从 0 关选择任意通道
skip[j],跳到skip[j],此时已获得b[0] + b[skip[j]](如果skip[j] < N)
终止条件
- 一旦跳到
pos ≥ N,游戏结束 - 所以答案 = 所有
dp[i]中,满足i ≥ N−1且通过某次跳跃能 跳到 ≥N 的最大值
更简单:因为从
i跳skip[j]后若i + skip[j] ≥ N就通关,所以只要i ∈ [N−1, N+max_skip−1]都可能是最后一步的起点
但代码采用更直接方式:计算所有 dp[i](i < N+max_s) ,然后在 [N−1, N+max_s−1] 中取最大值
⚠️ 特殊细节
- 数组
a[]实际存储的是b[](题目中的得分) - 代码中
a[i]对应题目中的b_i skip[]存储 M 个通道的跳跃长度
代码解析
cpp
#include<bits/stdc++.h>
using namespace std;
int a[20010], skip[110]; // a[i] 存 b_i(第 i 关的分数),skip 存 M 个跳跃步长
long long dp[20010]; // dp[i] 表示"到达位置 i 时已获得的最大总分"
- 数组大小足够:
N ≤ 10000,最大可能到达位置为N + max(skip) ≤ 20000。 dp[i]的含义:当你站在位置 i(准备从此跳走)时,已经拿到的总分 。- 如果
i < N,说明你刚离开第 i 关,已加a[i]。 - 如果
i ≥ N,说明你已通关,不再加分,dp[i]只是继承前驱值。
- 如果
cpp
int main(){
int N, M;
cin >> N >> M;
int max_s = 0; // 记录所有通道中最大的跳跃步长
for(int i = 0; i < M; i++){
cin >> skip[i];
max_s = max(max_s, skip[i]);
}
- 读入 N(关卡数)、M(通道数)。
- 读入 M 个跳跃步长
skip[0..M-1],并记录最大值max_s。 - 用途:确定 DP 数组需要计算到多远(
N + max_s)。
cpp
// 初始化 dp 数组:所有位置初始为"不可达"
for(int i = 0; i < N + max_s; i++)
dp[i] = INT_MIN;
INT_MIN表示"无法到达该位置"。- 范围
[0, N + max_s)覆盖了所有可能被跳到的位置(包括通关后的位置)。
cpp
for(int i = 0; i < N; i++)
cin >> a[i]; // 读入每关的分数 b_i,存入 a[i]
a[i]即题目中的b_i:离开第 i 关时获得的分数。- 注意:
a[i]只定义在i = 0 ~ N-1,i ≥ N时无意义(但全局初始化为 0)。
cpp
// 初始化:从第 0 关出发,尝试所有通道
for(int i = 0; i < M; i++)
dp[skip[i]] = a[skip[i]] + a[0];
- 起点在第 0 关,必须先"离开"它才能得分 → 获得
a[0]。 - 选择通道
skip[i]:- 跳到位置
skip[i]。 - 如果
skip[i] < N,则你也离开了第skip[i]关 → 再得a[skip[i]]。 - 所以总得分 =
a[0] + a[skip[i]]。
- 跳到位置
- 如果
skip[i] ≥ N,则跳完就通关,只应得a[0]。- 但此时
a[skip[i]]是未定义的(全局为 0),所以a[0] + 0 = a[0]→ 结果正确。
- 但此时
- 因此,这一行统一处理了所有初始跳跃。
cpp
dp[0] = a[0]; // 站在第 0 关(准备跳)时,已获得 a[0] 分
- 这是为了让后续 DP 能从位置 0 转移。
- 虽然游戏一开始在第 0 关,但只有离开它才得分 ,所以
dp[0] = a[0]表示"已准备好从 0 出发,并已计入 a[0]"。
🎯 这样设计后,DP 转移可以统一写成:
dp[i] = max(dp[i - skip[j]] + a[i])其中
a[i]在i ≥ N时为 0,自动忽略无效加分。
cpp
// 动态规划主循环
for(int i = 1; i < N + max_s; i++){
for(int j = 0; j < M; j++){
if(i >= skip[j] && dp[i - skip[j]] != INT_MIN){
dp[i] = max(dp[i], a[i] + dp[i - skip[j]]);
}
}
}
- 枚举当前位置
i(从 1 到N + max_s - 1)。 - 枚举每个通道
skip[j]:- 如果可以从
i - skip[j]跳skip[j]步到达i(即i ≥ skip[j]), - 且
i - skip[j]是可达的(dp[i - skip[j]] != INT_MIN), - 那么尝试更新
dp[i]:- 新得分 = 前驱得分
dp[i - skip[j]]+ 当前位置得分a[i] - 注意:若
i ≥ N,a[i] = 0(全局初始化),所以不加分 → 正确!
- 新得分 = 前驱得分
- 如果可以从
✅ 这个转移覆盖了所有可能路径,包括多次跳跃。
cpp
// 寻找答案:所有能导致通关的位置中,取最大得分
long long ans = INT_MIN;
for(int i = N - 1; i < N + max_s; i++)
ans = max(ans, dp[i]);
cout << ans;
- 为什么从
i = N - 1开始?- 因为从
i = N - 1跳任意≥1步都会 ≥ N → 通关。 - 从更早的位置(如
N - 2)也可能跳到 ≥N,但那些位置的dp值会在i = N - 2 + skip[j] ≥ N时被复制到dp[i](因为a[i] = 0)。
- 因为从
- 所以,所有能一步跳到通关的位置 x(x < N)的得分
dp[x],都会出现在dp[N ... N+max_s-1]中。 - 同时,
dp[N-1]本身也可能是答案(如果从它跳 1 步通关)。 - 因此,在
[N-1, N+max_s)范围内取最大值,就能覆盖所有可能的最终得分。
总结
| 设计点 | 说明 |
|---|---|
| 统一状态定义 | dp[i] 表示"站在位置 i 时的总得分",无论 i 是否 ≥N |
| 利用全局初始化 | a[i] = 0(i ≥ N)自动处理"通关后不加分" |
| 扩展状态空间 | 计算到 N + max_s,确保所有"最后一步"的得分都被记录 |
| 起点处理巧妙 | dp[0] = a[0] + 初始化 dp[skip[i]],确保第一跳正确 |