P10108 [GESP202312 六级] 闯关游戏

记录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 的最大值

更简单:因为从 iskip[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-1i ≥ 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 ≥ Na[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]],确保第一跳正确
相关推荐
kvo7f2JTy2 小时前
吃透Linux/C++系统编程:文件与I/O操作从入门到避坑
java·linux·c++
Lzh编程小栈2 小时前
数据结构与算法之队列深度解析:循环队列+C 语言硬核实现 + 面试考点全梳理
c语言·开发语言·汇编·数据结构·后端·算法·面试
崽崽..2 小时前
【面经】shared_ptr的线程安全问题
c++
AbandonForce2 小时前
模拟实现vector
开发语言·c++·算法
少许极端2 小时前
算法奇妙屋(四十二)-贪心算法学习之路 9
学习·算法·贪心算法
CoderCodingNo2 小时前
【NOIP】1998真题解析 luogu-P1010 幂次方 | GESP四、五级以上可练习
算法
py有趣2 小时前
力扣热门100题之最小覆盖子串
算法·leetcode
汀、人工智能2 小时前
[特殊字符] 第102课:添加与搜索单词
数据结构·算法·均值算法·前缀树·trie·添加与搜索单词
汀、人工智能2 小时前
07 - 字典dict:哈希表的Python实现
数据结构·算法·数据库架构·哈希表的python实现