CSP-J系列【2023】P9749 [CSP-J 2023] 公路

题目描述

小苞准备开着车沿着公路自驾。

公路上一共有 n 个站点,编号为从 1 到 n。其中站点 i 与站点 i+1 的距离为 vi​ 公里。

公路上每个站点都可以加油,编号为 i 的站点一升油的价格为 ai​ 元,且每个站点只出售整数升的油。

小苞想从站点 1 开车到站点 n,一开始小苞在站点 1 且车的油箱是空的。已知车的油箱足够大,可以装下任意多的油,且每升油可以让车前进 d 公里。问小苞从站点 1 开到站点 n,至少要花多少钱加油?

输入格式

输入的第一行包含两个正整数 n 和 d,分别表示公路上站点的数量和车每升油可以前进的距离。

输入的第二行包含 n−1 个正整数 v1​,v2​...vn−1​,分别表示站点间的距离。

输入的第三行包含 n 个正整数 a1​,a2​...an​,分别表示在不同站点加油的价格。

输出格式

输出一行,仅包含一个正整数,表示从站点 1 开到站点 n,小苞至少要花多少钱加油。

输入输出样例

输入 #1复制

复制代码
5 4
10 10 10 10
9 8 9 6 5

输出 #1复制

复制代码
79

说明/提示

【样例 1 解释】

最优方案下:小苞在站点 1 买了 3 升油,在站点 2 购买了 5 升油,在站点 4 购买了 2 升油。

【样例 2】

见选手目录下的 road/road2.in 与 road/road2.ans。

【数据范围】

对于所有测试数据保证:1≤n≤105,1≤d≤105,1≤vi​≤105,1≤ai​≤105。

测试点 n≤ 特殊性质
1∼5 8
6∼10 103
11∼13 105 A
14∼16 105 B
17∼20 105
  • 特殊性质 A:站点 1 的油价最低。
  • 特殊性质 B:对于所有 1≤i<n,vi 为 d 的倍数。

题目重述

给定一条有n个站点的公路,每个站点有油价v[i]和距离下一个站点的距离d[i]。车辆油箱容量无限但初始为空,求从第1个站点到第n个站点的最小加油费用。

解法一:贪心算法(向前寻找最优加油站)

算法原理

  1. 核心思想:在当前加油站寻找后面第一个油价更低的站点

  2. 策略:在当前站点加刚好能到达更便宜加油站的油量

  3. 时间复杂度:O(n²) 最坏情况下

  4. 空间复杂度:O(n)

实现细节

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n, d;
    cin >> n >> d;
    vector<int> v(n-1), a(n);
    
    for(int i=0; i<n-1; i++) cin >> v[i];
    for(int i=0; i<n; i++) cin >> a[i];
    
    long long cost = 0, oil = 0;
    int min_price = a[0];
    
    for(int i=0; i<n-1; i++) {
        if(a[i] < min_price) min_price = a[i];
        int need = (v[i] - oil + d - 1) / d;
        cost += need * min_price;
        oil += need * d - v[i];
    }
    cout << cost << endl;
    return 0;
}

解法二:单调栈优化(线性时间复杂度)

算法原理

  1. 核心思想:维护一个油价单调递减的栈

  2. 优化点:避免重复比较,将时间复杂度降至O(n)

  3. 空间复杂度:O(n) 用于存储栈

实现细节

cpp 复制代码
#include <iostream>
#include <vector>
#include <stack>
using namespace std;

int main() {
    int n, d;
    cin >> n >> d;
    vector<int> v(n-1), a(n);
    
    for(int i=0; i<n-1; i++) cin >> v[i];
    for(int i=0; i<n; i++) cin >> a[i];
    
    stack<int> st;
    st.push(0);
    vector<int> next_lower(n, n-1);
    
    for(int i=1; i<n; i++) {
        while(!st.empty() && a[i] < a[st.top()]) {
            next_lower[st.top()] = i-1;
            st.pop();
        }
        st.push(i);
    }
    
    long long cost = 0, sum_dist = 0;
    for(int i=0; i<n-1; ) {
        int j = next_lower[i];
        long long dist = 0;
        for(int k=i; k<=j && k<n-1; k++) dist += v[k];
        int need = (dist + d - 1) / d;
        cost += need * a[i];
        i = j + 1;
    }
    cout << cost << endl;
    return 0;
}

对比分析

特性 贪心算法 单调栈优化
时间复杂度 O(n²) O(n)
空间复杂度 O(1) O(n)
代码复杂度 简单 中等
适用场景 小规模数据 大规模数据

算法正确性证明

  1. 贪心选择性:每次选择局部最优解(最近更便宜的加油站)能保证全局最优

  2. 最优子结构:问题的最优解包含子问题的最优解

  3. 数学归纳法:可通过归纳法证明贪心策略的正确性

边界条件处理

  1. 最后一个站点无需加油

  2. 距离数组长度为n-1

  3. 油量可能超出int范围,需使用long long

复杂度优化思考

  1. 距离预处理前缀和可进一步优化距离计算

  2. 单调栈实现可以进一步简化代码

  3. 输入输出使用快速IO可提升程序性能

算法核心总结

  1. 贪心算法特性
  • 实时维护当前最低油价

  • 分段计算油量需求(向上取整)

  • 剩余油量自动抵扣下一段路程

  • 时间复杂度O(n)实现高效计算

  1. 单调栈优化亮点
  • 预处理建立油价控制区间

  • 通过next_lower数组明确各站点影响范围

  • 批量计算区间内总油量需求

  • 相同时间复杂度下逻辑更清晰

关键公式说明

  • 油量计算:(距离 + d - 1) / d (确保整数升)

  • 单调栈转移:next_lower[i] = j (表示i站油价控制到j站)

实际应用价值

  1. 物流运输领域:优化加油站选择策略

  2. 行程规划场景:自驾游油费最小化方案

  3. 算法教学范例:展示贪心与单调栈的典型应用

  4. 竞赛解题思路:提供同类区间优化问题的解决范式

代码实现建议

  1. 优先掌握贪心解法(代码更简洁)

  2. 理解单调栈的预处理思想(拓展性强)

  3. 注意变量类型用long long防溢出

  4. 测试用例应包含:

    • 单调递减油价

    • 单调递增油价

    • 波动油价

    • 单站点特殊情况

相关推荐
啊我不会诶1 小时前
CF每日5题(1500-1600)
c++·学习·算法
巴伦是只猫1 小时前
Java 高频算法
java·开发语言·算法
点云SLAM2 小时前
C++中std::string和std::string_view使用详解和示例
开发语言·c++·算法·字符串·string·c++标准库算法·string_view
88号技师2 小时前
2025年7月Renewable Energy-冬虫夏草优化算法Caterpillar Fungus Optimizer-附Matlab免费代码
开发语言·人工智能·算法·matlab·优化算法
dragoooon343 小时前
优选算法:移动零
c++·学习·算法·学习方法
运维小文3 小时前
初探贪心算法 -- 使用最少纸币组成指定金额
c++·python·算法·贪心算法
智者知已应修善业4 小时前
【C# 找最大值、最小值和平均值及大于个数和值】2022-9-23
经验分享·笔记·算法·c#
Zz_waiting.4 小时前
Java 算法解析 - 双指针
java·开发语言·数据结构·算法·leetcode·双指针
overFitBrain5 小时前
数据结构-4(常用排序算法、二分查找)
linux·数据结构·算法
Sagittarius_A*6 小时前
【C++】标准模板库(STL)—— 学习算法的利器
c++·学习·算法·stl