97 lines
3.4 KiB
Markdown
97 lines
3.4 KiB
Markdown
### [$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 <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 总结
|
||
本题的题目具有一定的迷惑性,具体模拟一遍开车加油的流程就可以发现,最优化的加油方案只有一种,就是每一步都找到花费最小的加油站,然后开车过去即可。
|
||
|
||
因此,贪心加模拟可以解决此问题。 |