2 条题解

  • 0
    @ 2025-11-28 10:04:57

    这是一份标准的 AC 代码。

    核心解题思路回顾

    1. 贪心策略:走到第 ii 站时,如果当前站点的油价比之前遇到的最低油价 min_price 还要低,就更新 min_price。无论我们在哪个站点,加油时总是按“目前为止见过的最低价”来结算(相当于在之前便宜的站点预先多加了油)。
    2. 剩余油量管理:因为必须买整数升油,跑完一段路后往往会有剩余的油。这部分油(rem_dist)要存起来,用于抵扣下一段路程。
    3. 数据类型:总金额和距离会超过 int 范围,必须使用 long long

    标准 C++ 代码

    #include <iostream>
    #include <vector>
    #include <cmath>
    #include <algorithm> // 用于 min 函数
    
    using namespace std;
    
    int main() {
        // 1. 优化输入输出速度(虽非必须,但对于大量数据是个好习惯)
        ios::sync_with_stdio(false);
        cin.tie(0);
    
        // 2. 定义变量
        // n: 站点数, d: 每升油能跑的距离
        // 注意:d 参与乘法运算,建议用 long long 防止溢出,虽然题目范围 1e5 int 也够
        int n;
        long long d; 
        cin >> n >> d;
    
        // 3. 读入数据
        // v数组:存储站点间的距离,共有 n-1 段
        // a数组:存储站点的油价,共有 n 个
        vector<long long> v(n - 1);
        for (int i = 0; i < n - 1; ++i) {
            cin >> v[i];
        }
    
        vector<int> a(n);
        for (int i = 0; i < n; ++i) {
            cin >> a[i];
        }
    
        // 4. 模拟与贪心过程
        long long ans = 0;        // 总花费(必须 long long)
        long long rem_dist = 0;   // 油箱里剩余油量能跑的距离(必须 long long)
        int min_price = a[0];     // 记录当前遇到的最低油价,初始为第1站的价格
    
        // 遍历每一段路程 (从站点 1 到 站点 n-1)
        // i 代表当前所在的站点下标,我们要从 i 走到 i+1
        for (int i = 0; i < n - 1; ++i) {
            // 【核心贪心】:到了站点 i,看看这里的油是不是比之前见过的都便宜
            // 如果是,以后就按这里的价格算(相当于在这里多加点油)
            // 如果不是,保持之前的最低价(相当于用之前在便宜站点买的囤油)
            if (a[i] < min_price) {
                min_price = a[i];
            }
    
            long long need_dist = v[i]; // 这一段路需要的距离
    
            // 如果剩余油量足够跑完这段路
            if (rem_dist >= need_dist) {
                rem_dist -= need_dist;
            } 
            // 如果剩余油量不够
            else {
                // 1. 算出还缺多少距离
                long long lack = need_dist - rem_dist;
                
                // 2. 算出至少买多少升油 (向上取整)
                // 公式:(a + b - 1) / b 等价于 ceil(a / b)
                long long liters = (lack + d - 1) / d;
    
                // 3. 累加花费
                ans += liters * min_price;
    
                // 4. 更新剩余油量
                // 新的剩余 = (旧剩余 + 新买的距离) - 这一段消耗的距离
                rem_dist += liters * d;
                rem_dist -= need_dist;
            }
        }
    
        // 5. 输出结果
        cout << ans << endl;
    
        return 0;
    }
    

    易错点检查

    1. 向上取整公式:计算买油量时使用 (lack + d - 1) / d 是最稳妥的整数运算写法,不要用 ceil 浮点数运算,容易有精度误差。
    2. long longans(总花费)和 rem_dist(累计剩余距离)一定要开 long long,因为 105×10510^5 \times 10^5 会超过 23112^{31}-1
    3. 循环范围:距离数组 v 只有 n-1 个元素,循环是 0n-2(对应站点 121 \to 2 直到站点 n1nn-1 \to n)。
    • 0
      @ 2025-11-28 10:03:12

      你好!我是你的 OI 教练。

      这道题(CSP-J 2023 T2)是一道非常经典的贪心算法题目。只要你想通了“什么时候加油最划算”这个问题,代码其实非常短。

      下面我为你梳理几个核心的解题思路和避坑指南:

      1. 核心策略:贪心(寻找“当前最低价”)

      题目告诉我们要从站点 1 开到站点 nn,且油箱无限大。这意味着我们可以囤油。

      想象一下你开到了第 ii 个站点,你需要前往第 i+1i+1 个站点。你现在的油箱里可能还有剩下的油,也可能不够了。如果不够,你必须加油。那么,用哪个站点的油价来算这笔钱呢?

      • 如果你现在的站点 ii 的油价,比之前经过的所有站点(11i1i-1)都要便宜,那肯定是在当前站点 ii 加油最划算。
      • 如果你现在的站点 ii 的油价很高,比你之前路过的某个站点(比如站点 kk)贵。既然油箱无限大,那你当初在站点 kk 的时候,就应该多加一点油,这就相当于你现在用站点 kk 的便宜价格在跑现在的路。

      结论: 当我们走到第 ii 个站点,准备去往第 i+1i+1 个站点时,我们结算的油价应该是从第 1 站到第 ii 站中出现的最低油价。 你需要维护一个变量(比如 min_price),记录“一路上见过的最便宜的油价”。

      2. 难点处理:整数升油与“剩余油量”

      题目有一个非常讨厌的限制:只能购买整数升的油。 这意味着,如果你只需要跑 1 公里,而 1 升油能跑 10 公里,你也必须买 1 升油。跑完这 1 公里后,你的油箱里还剩相当于 9 公里的油。

      这剩下的油不能浪费! 它必须被记录下来,用于抵扣下一段路程。

      模拟过程:

      1. 定义一个变量 rem (remainder),记录当前油箱里的油还能跑多少公里(初始为 0)。
      2. 定义 min_price,记录目前为止最低油价。
      3. 循环遍历每一段路程(从 1 到 n1n-1):
        • 更新 min_pricemin_price = min(min_price, 当前站点油价)
        • 获取当前路段距离 dist = v[i]
        • 先用剩下的油跑:如果 rem >= dist,说明剩下的油够跑,不需要花钱。更新 rem -= dist,直接进入下一轮。
        • 如果剩下的油不够跑
          • 计算还需要跑多远:need_dist = dist - rem
          • 计算需要买几升油:num = ceil(need_dist / d)
            • C++技巧:整数向上取整 (a + b - 1) / b。这里就是 (need_dist + d - 1) / d
          • 计算花费:ans += num * min_price
          • 关键更新:你的油箱里现在的油变了。
            • 你加了 num 升油,能跑 num * d 公里。
            • 加上原来剩的 rem 公里。
            • 跑掉了 dist 公里。
            • 新剩余:rem = rem + num * d - dist

      3. 数据范围与类型(必看!)

      请看数据范围:

      • n105n \le 10^5
      • vi105v_i \le 10^5
      • 总路程可能会达到 105×105=101010^5 \times 10^5 = 10^{10} 公里。

      警告

      • 普通的 int 类型最大只能存约 2×1092 \times 10^9
      • 总花费和总路程都会超过 int 的范围。
      • 必须使用 long long 来存储你的答案(ans)!

      4. 代码结构框架

      #include <iostream>
      #include <cmath>
      using namespace std;
      
      int main() {
          // 1. 定义变量,注意数据范围,答案要用 long long
          int n;
          long long d; // d 用 long long 防止计算乘法溢出
          cin >> n >> d;
          
          // 2. 读入距离数组 v[] 和 油价数组 a[]
          // 建议使用数组存储,方便循环访问
          
          // 3. 初始化
          long long ans = 0;      // 总花费
          long long rem = 0;      // 剩余油量能跑的距离
          int min_price = 1e9;    // 当前最低油价,初始化为一个大数
          
          // 4. 循环处理每一段路 (从站点 1 到 n-1)
          for (int i = 1; i <= n - 1; i++) {
              // a. 拿到当前站点的油价 a[i]
              // b. 更新 min_price = min(min_price, a[i])
              
              // c. 拿到这一段的距离 v[i] (注意数组下标对应关系)
              
              // d. 判断 rem 是否足够
              //    如果够:rem -= v[i]
              //    如果不够:
              //       计算缺多少距离
              //       计算要买几升油 (向上取整)
              //       累加花费 ans += 买油数 * min_price
              //       更新 rem (加上新买的油能跑的距离,减去这段路程)
          }
          
          // 5. 输出 ans
          cout << ans << endl;
          
          return 0;
      }
      

      这道题其实就是在模拟开车的每一步,只要把“多出来的油留到下一段用”这个逻辑处理好,就一定能 AC。加油!

      • 1

      信息

      ID
      14099
      时间
      1000ms
      内存
      32MiB
      难度
      3
      标签
      递交数
      1
      已通过
      1
      上传者