题目描述
小苞准备开着车沿着公路自驾。
公路上一共有 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个站点的最小加油费用。
解法一:贪心算法(向前寻找最优加油站)
算法原理
-
核心思想:在当前加油站寻找后面第一个油价更低的站点
-
策略:在当前站点加刚好能到达更便宜加油站的油量
-
时间复杂度:O(n²) 最坏情况下
-
空间复杂度: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;
}
解法二:单调栈优化(线性时间复杂度)
算法原理
-
核心思想:维护一个油价单调递减的栈
-
优化点:避免重复比较,将时间复杂度降至O(n)
-
空间复杂度: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) |
代码复杂度 | 简单 | 中等 |
适用场景 | 小规模数据 | 大规模数据 |
算法正确性证明
-
贪心选择性:每次选择局部最优解(最近更便宜的加油站)能保证全局最优
-
最优子结构:问题的最优解包含子问题的最优解
-
数学归纳法:可通过归纳法证明贪心策略的正确性
边界条件处理
-
最后一个站点无需加油
-
距离数组长度为n-1
-
油量可能超出int范围,需使用long long
复杂度优化思考
-
距离预处理前缀和可进一步优化距离计算
-
单调栈实现可以进一步简化代码
-
输入输出使用快速IO可提升程序性能
算法核心总结
- 贪心算法特性
-
实时维护当前最低油价
-
分段计算油量需求(向上取整)
-
剩余油量自动抵扣下一段路程
-
时间复杂度O(n)实现高效计算
- 单调栈优化亮点
-
预处理建立油价控制区间
-
通过next_lower数组明确各站点影响范围
-
批量计算区间内总油量需求
-
相同时间复杂度下逻辑更清晰
关键公式说明
-
油量计算:(距离 + d - 1) / d (确保整数升)
-
单调栈转移:next_lower[i] = j (表示i站油价控制到j站)
实际应用价值
-
物流运输领域:优化加油站选择策略
-
行程规划场景:自驾游油费最小化方案
-
算法教学范例:展示贪心与单调栈的典型应用
-
竞赛解题思路:提供同类区间优化问题的解决范式
代码实现建议
-
优先掌握贪心解法(代码更简洁)
-
理解单调栈的预处理思想(拓展性强)
-
注意变量类型用long long防溢出
-
测试用例应包含:
-
单调递减油价
-
单调递增油价
-
波动油价
-
单站点特殊情况
-