### [$P9749$ [$CSP-J$ $2023$] 公路](https://www.luogu.com.cn/problem/P9749?contestId=140858) ### 题目分析 题目较长,需要反复阅读才能理解题意,但解题的关键也在于理解题意。 简单描述题目为:开车从$1$ 走到 $n$, 路过站点时可以加油,每个站点的油价不同,有高有低,如何加油可以使 **总费用最低**? 考虑最开始的情况:在起始位置,车辆一滴油都没有,所以在 $a[1]$ 站点必须要加油。那么加多少呢? 很自然根据贪心思想,找到下一个站点 $a[j]$, 如果 $a[j]$ 比 $a[1]$ 价格便宜,就可以在 $a[j]$ 加油。 因此,程序的主体算法可以用伪代码表示为: ``` while (车辆还没有跑到终点) { 找到下一个便宜的加油站 a[j] 加的油刚好能到站点 a[j] 累加费用 } 输出总费用 ``` 可以简单证明一下贪心算法的合理性: - 从第一站开始,只有一种最优的加油方案,就是加最少的油跑到下一个便宜的加油站。 - 以此类推,跑完全程,每一次加油都是最便宜的方案,因此总方案也是最便宜的。 #### 实现细节 从节点 $[i]$ 跑到节点 $[j]$ 油箱里的油 **可能是有剩余的**,编码时需要把剩余的油量考虑在内。有点类似计算加减法的进位和借位。 计算从 $i$ 到 $j$ 的费用可以表示为: ``` int 计算费用(int i, int j) { 1. 计算 i -> j 的路程 if (油箱里剩余的油足够) { 不用加油,油箱里的油减少 费用为 0 } else { 需要加油,计算加多少油,需要考虑油箱剩余的油 计算加油费用 跑到 j 时,油箱的油还能跑几公里,保存到全局变量 } 返回费用 } ``` ```cpp {.line-numbers} #include using namespace std; #define int long long const int N = 100010; int v[N]; // 站点间的距离 int a[N]; // 每个站点的油价 int n; // n个站点 double d; // 1升油可以跑多少公里 int money; // 一共花费 int r; // 油箱的油可以跑的公里数 int getMoney(int begin, int end) { // 左闭右开 int length = 0; for (int i = begin; i < end; i++) length += v[i]; if (r >= length) { // 不用加油 r -= length; return 0; } else { // 需要加油, 计算加多少油: 油箱的油还有剩余,还能跑几公里 int add = ceil((length - r) / d); // 计算加油费用 int x = a[begin] * add; // 跑到 end, 油箱的油还能跑几公里 r = add * d + r - length; return x; } } signed main() { cin >> n >> d; // n个站点,每升油可以让车前进d公里 for (int i = 1; i < n; i++) cin >> v[i]; // 站点间的距离 a1~a2,a2~a3,... for (int i = 1; i <= n; i++) cin >> a[i]; // 不同站点加油的价格 int i = 1; while (i < n) { int j = i + 1; // 找到下一个便宜的加油站 while (j <= n && a[j] >= a[i]) j++; money += getMoney(i, j); i = j; } cout << money << endl; } ``` 3 总结 本题的题目具有一定的迷惑性,具体模拟一遍开车加油的流程就可以发现,最优化的加油方案只有一种,就是每一步都找到花费最小的加油站,然后开车过去即可。 因此,贪心加模拟可以解决此问题。