Files
python/TangDou/CSP-J/J-2023/road.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

3.4 KiB
Raw Blame History

P9749 [CSP-J $2023$] 公路

题目分析

题目较长,需要反复阅读才能理解题意,但解题的关键也在于理解题意。

简单描述题目为:开车从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 时,油箱的油还能跑几公里,保存到全局变量
    }
    返回费用
}
#include <bits/stdc++.h>
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 总结 本题的题目具有一定的迷惑性,具体模拟一遍开车加油的流程就可以发现,最优化的加油方案只有一种,就是每一步都找到花费最小的加油站,然后开车过去即可。

因此,贪心加模拟可以解决此问题。