You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

97 lines
3.4 KiB

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