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