记录133
#include<bits/stdc++.h>
using namespace std; // 使用标准命名空间std
const int N=1e5+5; // 定义常量N,表示数组长度的最大范围
int n; // 定义全局变量n,表示数组的长度
int a[N],b[N]; // 定义数组a和b,分别存储题目给定的两个数组
long long f[N],ans; // 定义f数组记录到达某位置的最大分数,ans记录全局最大和
int main(){ // 正向递推(刷表法)
ios::sync_with_stdio(false); // 关闭标准流同步,提升输入输出效率
cin.tie(0); // 解除cin与cout的绑定,加快读取速度
cin>>n; // 读入数组长度n
for(int i=1;i<=n;i++){ // 循环读入数组a
cin>>a[i]; // 读入a[i]
}
for(int i=1;i<=n;i++){ // 循环读入数组b
cin>>b[i]; // 读入b[i]
}
// 正向递推(刷表法):利用当前下标i的状态,去更新后续位置的状态
for(int i=1;i<=n;i++){
// 尝试选择当前下标i:更新全局答案ans
ans=max(ans,f[i]+a[i]);
// 如果选择当前下标i,可以跳跃到 i+b[i] 的位置,更新该位置的最大分数
if(i+b[i]<=n){
f[i+b[i]]=max(f[i+b[i]],f[i]+a[i]);
}
// 不选择当前下标i:将当前的最大分数直接传递给下一个位置 i+1
f[i+1]=max(f[i+1],f[i]);
}
cout<<ans<<"\n"; // 输出在满足下标条件的前提下,a数组对应下标的整数之和的最大值
return 0; // 程序正常结束
}
题目传送门
https://www.luogu.com.cn/problem/P15800
前言
我是一名专注信奥赛(CSP-J/S、NOIP)的教练。
- 如果你觉得这篇题解对你有帮助,欢迎点击关注我的CSDN账号,我会持续更新高质量算法解析。
- 我深知算法思维的构建远比单纯通过题目更重要,本系列题解不局限于AC代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
- 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。
核心解题思路
这道题是一道非常经典的线性动态规划(DP)问题,其核心难点在于理解下标之间的跳跃约束。
-
状态定义:
我们定义f[i]表示:当我们处理到第i个位置时,能够获得的最大分数(注意:f[i]代表的是从前面某个位置转移过来,准备在i或之后的位置进行决策时的累积得分,它本身不包含a[i])。 -
约束条件的转化:
题目要求如果选择了下标p_i,那么下一个下标p_{i+1}必须满足p_{i+1} >= p_i + b[p_i]。这意味着,如果我们当前站在位置i并决定选择它,那么我们下一次能够进行决策的最早位置就是i + b[i]。 -
状态转移(刷表法):
对于每一个位置i,我们有两种选择:- 选择位置 i:我们将
a[i]加入总分,并将这个新的总分传递给下一个合法的位置i + b[i]。即f[i + b[i]] = max(f[i + b[i]], f[i] + a[i])。同时,因为选择了i,我们可以用f[i] + a[i]去更新全局的最大答案ans。 - 不选择位置 i:如果我们放弃位置
i,那么当前累积的分数f[i]可以原封不动地传递给下一个相邻位置i + 1。即f[i + 1] = max(f[i + 1], f[i])。
- 选择位置 i:我们将
-
最终答案:
在遍历过程中,我们随时记录f[i] + a[i]的最大值,最终输出这个全局最大值即可。
代码分块详细解释
1. 头文件、常量与全局变量定义
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5; // 定义常量 N,表示数组长度的最大范围
int n; // 定义全局变量 n,表示数组的长度
int a[N], b[N]; // 定义数组 a 和 b,分别存储题目给定的两个数组
long long f[N], ans; // 定义 f 数组记录到达某位置的最大分数,ans 记录全局最大和
- 详细分析:这部分搭建了算法的基础。由于数组长度
n最大为 10^5,且a_i最大可达 10^9,累加后的总分数可能会超过int的范围,因此f数组和答案ans必须使用long long类型来防止数据溢出。
2. 输入处理
int main(){
ios::sync_with_stdio(false); // 关闭标准流同步,提升输入输出效率
cin.tie(0); // 解除 cin 与 cout 的绑定,加快读取速度
cin>>n; // 读入数组长度 n
for(int i=1;i<=n;i++){ // 循环读入数组 a
cin>>a[i]; // 读入 a[i]
}
for(int i=1;i<=n;i++){ // 循环读入数组 b
cin>>b[i]; // 读入 b[i]
}
- 详细分析:在读取数据之前,使用了标准的 IO 加速语句。在
n达到 10^5 级别时,这能有效避免因为输入输出过慢而导致的超时。随后依次读入两个数组的数据,为接下来的动态规划做准备。
3. 核心逻辑:正向递推(刷表法)
// 正向递推(刷表法):利用当前下标 i 的状态,去更新后续位置的状态
for(int i=1;i<=n;i++){
// 尝试选择当前下标 i:更新全局答案 ans
ans = max(ans, f[i] + a[i]);
// 如果选择当前下标 i,可以跳跃到 i + b[i] 的位置,更新该位置的最大分数
if(i + b[i] <= n){
f[i + b[i]] = max(f[i + b[i]], f[i] + a[i]);
}
// 不选择当前下标 i:将当前的最大分数直接传递给下一个位置 i+1
f[i + 1] = max(f[i + 1], f[i]);
}
- 详细分析:这是整个算法的灵魂,采用了“刷表法”(即由当前状态去更新未来状态)。
- 更新全局最优解:
f[i]表示到达位置i之前(不包含i)的最大累积分数。如果我们在位置i停下并选择它,那么当前的总分就是f[i] + a[i]。我们随时用这个值去挑战全局最大值ans。 - 跳跃转移(选择 i):如果决定选择
a[i],根据题目约束p_{i+1} >= p_i + b[p_i],下一个能够做决策的位置至少是i + b[i]。我们将当前获得的总分f[i] + a[i]传递给f[i + b[i]],并取最大值。 - 相邻转移(不选 i):如果我们决定放弃
a[i],那么位置i就没有产生任何价值,也没有触发跳跃限制。因此,到达位置i的累积分数f[i]可以直接顺延到下一个位置i + 1。这保证了我们可以连续跳过某些位置。
- 更新全局最优解:
4. 输出结果
cout << ans << "\n"; // 输出在满足下标条件的前提下,a 数组对应下标的整数之和的最大值
return 0; // 程序正常结束
}
- 详细分析:在遍历完所有位置后,
ans中存储的就是所有合法下标组合中,a数组对应下标整数之和的最大值,直接输出即可。
核心逻辑总结表
| 代码模块 | 核心变量/操作 | 精炼作用 | 解决的痛点 |
|---|---|---|---|
| 数据类型 | long long f[N], ans | 使用 64 位长整型 | 防止 a_i (最大 109) 累加时超出 int 范围导致数据溢出 |
| IO 优化 | ios::sync_with_stdio(false) | 提升输入输出效率 | 解决 10^5 级别数据量下 cin/cout 可能导致的超时问题 |
| 状态定义 | f[i] | 记录到达位置 i 时的最大累积分数 | 将复杂的下标跳跃约束,转化为线性的状态传递问题 |
| 跳跃转移 | f[i+b[i]] = max(...) | 处理“选择当前下标”的情况 | 完美契合题目中 p_{i+1} >= p_i + b[p_i] 的约束条件 |
| 顺延转移 | f[i+1] = max(f[i+1], f[i]) | 处理“不选当前下标”的情况 | 允许程序跳过某些下标,保证决策的灵活性 |
| 全局打擂 | ans = max(ans, f[i]+a[i]) | 实时更新最终答案 | 确保能够捕获到遍历过程中出现的任何合法的最大分数 |
404

被折叠的 条评论
为什么被折叠?



