1 条题解
-
0
P9749 [CSP-J 2023] 公路 题解与代码详解
一、题目核心思想
这道题是一道经典的贪心算法入门题,最优解完全基于一个极其简单的贪心策略,也就是你说的:
永远用目前见过的最便宜的油来跑每一段路。
我们从起点出发,每到达一个加油站,先更新我们目前见过的最低油价。然后开往下一个加油站时,如果车里还有油就先用剩余的;如果不够,就按照目前见过的最低油价来买油,不管我们现在在哪个加油站。
因为题目中明确说明油箱无限大,所以我们可以在任何时候买油,然后在任何时候用。油的价格是固定的,什么时候用不影响总花费。这个策略是100%正确的,而且是所有解法中最简单、最优雅的一个。
二、贪心策略通俗解释
你可以把这个过程想象成:
- 你开车上路,随身带一个小本子,专门记录你见过的最便宜的油价
- 每到一个新的加油站,先看看这个加油站的油价比你本子上记的便宜吗?如果便宜,就更新本子上的价格
- 然后你要开往下一个地方,看看车里的油够不够
- 如果够,直接开过去就行
- 如果不够,你就按照本子上记的那个最便宜的价格来买油,买够能开到下一个地方的量
- 重复这个过程,直到到达终点
三、为什么这个策略一定是最优的?
反证法:假设存在一段路程,你用了价格为x的油来跑,但其实你之前可以买到价格为y的油,且y < x。那么,你把这部分油换成y元的油,总花费就会减少,这与"最优解"矛盾。
所以,每一段路程都用目前为止最便宜的油来跑,一定能得到全局最优解。
四、带完整注释的代码
#include<bits/stdc++.h> using namespace std; // 题目中n最大为1e5,数组开1e5+10防止越界 const int MAXN = 1e5 + 10; int main(){ // 加速cin输入,对于1e5的数据量必须加,否则可能超时 ios::sync_with_stdio(false); cin.tie(NULL); // n: 站点总数 // d: 每升油可以让车前进的公里数 int n,d; cin >> n >> d; // v[i]: 站点i到站点i+1之间的距离(公里) // 注意:只有n-1段路,所以i从1到n-1 int v[MAXN]; for(int i=1; i<n; i++){ cin >> v[i]; } // a[i]: 站点i的油价(元/升) // 注意:有n个站点,所以i从1到n int a[MAXN]; for(int i=1; i<=n; i++){ cin >> a[i]; } // ans: 总花费,必须用long long! // 最大可能花费:1e5段路 × 1e5公里/段 ÷ 1公里/升 × 1e5元/升 = 1e15 // 远远超过int的范围(约2e9) long long ans = 0; // m: 目前为止见过的最低油价,初始化为第一个站点的油价 int m = a[1]; // res: 当前油箱里的油还能行驶的公里数,初始化为0(一开始油箱是空的) int res = 0; // 逐段处理每一段路,从站点1到站点n,共n-1段路 for(int i=1; i<n; i++){ // 第一步:到达站点i,更新目前见过的最低油价 // 这就是你说的:"先记录到最便宜的油价" m = min(m, a[i]); // 第二步:准备开往下一个站点i+1,路程是v[i]公里 if(res >= v[i]){ // 情况1:剩余的油够跑这段路,直接用剩余的油 res -= v[i]; } else { // 情况2:剩余的油不够跑这段路,需要加油 // need: 还需要行驶多少公里才能跑完这段路 int need = v[i] - res; // t: 需要加多少升油 // 因为只能买整数升油,所以需要向上取整 // 公式:(a + b - 1) / b 是正整数a除以正整数b向上取整的通用写法 int t = (need + d - 1) / d; // 按照目前见过的最低油价m购买t升油,加到总花费 // 这里强制转换m为long long,防止m*t溢出int范围 ans += (long long)m * t; // 加完油后,计算新的剩余可行驶公里数 // 加了t升油,能跑t*d公里,加上原来剩余的res公里,减去跑这段路用掉的v[i]公里 res = t * d + res - v[i]; } } // 输出总花费 cout << ans << endl; return 0; }五、样例1模拟运行
我们用题目中的样例1来一步步验证这个思路和代码的正确性:
样例1输入
5 4 10 10 10 10 9 8 9 6 5运行过程
步骤(i) 当前站点 最低油价m 剩余可行驶res 路程v[i] 操作 总花费ans 新的res 初始 - 9 0 - 0 1 min(9,9)=9 10 油不够,需要10公里,加3升 0+9×3=27 3×4+0-10=2 2 min(9,8)=8 2 油不够,需要8公里,加2升 27+8×2=43 2×4+2-10=0 3 min(8,9)=8 0 油不够,需要10公里,加3升 43+8×3=67 3×4+0-10=2 4 min(8,6)=6 2 油不够,需要8公里,加2升 67+6×2=79 2×4+2-10=0 最终输出
79,和样例完全一致!六、代码优势说明
这个实现方式比传统的"用单调栈找下一个更小元素"的方法有三个巨大的优势:
- 逻辑更简单:不需要理解复杂的单调栈概念,只需要记住"永远用最便宜的油"
- 代码更短:只有一次遍历,没有任何预处理
- 更不容易出错:没有复杂的边界条件,考试中写得快、正确率高
七、考试注意事项
- 必须用long long存总花费:这是这道题最容易丢分的地方
- 必须加输入加速:对于1e5的数据量,不加
ios::sync_with_stdio(false)可能会超时 - 向上取整公式要记牢:
(a + b - 1) / b只适用于正整数 - 强制类型转换:计算
m*t时要把其中一个数转成long long,防止溢出
- 1
信息
- ID
- 4610
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 6
- 已通过
- 1
- 上传者
粤公网安备44195502000195号