按路程前缀计算到当前至少需要买多少整数升油,每次新增的升数都在此前最便宜的站点购买。
OJ: luogu
题目 ID: P9749
难度:普及/提高-
标签:贪心前缀和思维
日期: 2026-06-19 01:27
题意
一共有 n 个站点,站点 i 到 i+1 的距离是 v_i 公里。
每个站点都能加油,站点 i 的油价是 a_i 元/升。油箱容量无限大,但每个站点只能购买整数升油。
每升油能跑 d 公里。开始时车在 1 号站点,油箱为空,要求最少花多少钱从 1 号站点开到 n 号站点。
思路
先看一个可以直接验证想法的朴素解:
把“当前位置 + 还剩多少公里油量”当成状态,跑一遍最短路:
- 在当前站点买一升油,花
a_i元,油量增加d - 如果油量够,就开到下一站
这个做法在小数据上完全正确,也适合拿来对拍:
cpp
// brute.cpp:小数据最短路,状态为“到达哪个站点、还剩多少公里油量”。
#include <bits/stdc++.h>
using namespace std;
const long long INF = (1LL << 60);
const int MAXN = 25;
const int MAXF = 405;
struct Node {
long long cost;
int pos;
int fuel;
bool operator < (const Node &other) const {
return cost > other.cost;
}
};
int n, d;
int v[MAXN];
int a[MAXN];
long long dista[MAXN][MAXF];
int max_fuel;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> d;
for (int i = 1; i <= n - 1; i++) {
cin >> v[i];
}
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
max_fuel = 0;
for (int i = 1; i <= n - 1; i++) {
max_fuel += v[i];
}
// 因为只能按整升买油,最优解中油量可能会比“刚好够剩余路程”多出不到 d 公里。
max_fuel += d;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= max_fuel; j++) {
dista[i][j] = INF;
}
}
priority_queue<Node> q;
dista[1][0] = 0;
q.push((Node){0, 1, 0});
while (!q.empty()) {
Node cur = q.top();
q.pop();
if (cur.cost != dista[cur.pos][cur.fuel]) {
continue;
}
if (cur.pos == n) {
cout << cur.cost << '\n';
return 0;
}
// 在当前站点买一升油。
if (cur.fuel + d <= max_fuel && dista[cur.pos][cur.fuel + d] > cur.cost + a[cur.pos]) {
dista[cur.pos][cur.fuel + d] = cur.cost + a[cur.pos];
q.push((Node){dista[cur.pos][cur.fuel + d], cur.pos, cur.fuel + d});
}
// 油量足够时,直接开到下一站。
if (cur.fuel >= v[cur.pos] && dista[cur.pos + 1][cur.fuel - v[cur.pos]] > cur.cost) {
dista[cur.pos + 1][cur.fuel - v[cur.pos]] = cur.cost;
q.push((Node){cur.cost, cur.pos + 1, cur.fuel - v[cur.pos]});
}
}
return 0;
}但它的状态很多,不适合本题
关键观察是:油箱无限大,所以只要前面出现过更便宜的站点,我们就完全可以提前在那个站多买一些油带着走。
于是走到第 i 段路之后,我们真正关心的不是“油箱里此刻还剩几升”,而是:
- 到目前为止一共走了多少公里
- 这些路程总共至少需要买多少整数升油
设前 i 段路的总长度是 dist_sum,那么要覆盖这些路程,至少需要:
升油。
如果这个值比前面已经买过的总升数更多,就说明现在必须再补一些油。
而这些新增的升数,最优一定是在前 i 个站点里油价最低的那个站买。
所以从左到右扫描每一段路时:
- 维护到当前为止的最小油价
- 维护累计路程
dist_sum - 算出当前至少需要的总升数
- 如果
need比已经买过的升数更多,就把新增部分按当前最小油价计入答案
这样只要线性扫一遍就够了。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
int n, d;
int v[MAXN];
int a[MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> d;
for (int i = 1; i <= n - 1; i++) {
cin >> v[i];
}
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
long long ans = 0;
long long dist_sum = 0; // 到当前站点之后累计走了多少公里
long long bought = 0; // 到当前为止一共已经买了多少升油
int min_price = a[1];
for (int i = 1; i <= n - 1; i++) {
min_price = min(min_price, a[i]);
dist_sum += v[i];
// 走完前 i 段路后,至少需要 ceil(dist_sum / d) 升油。
long long need = (dist_sum + d - 1) / d;
if (need > bought) {
ans += (need - bought) * 1LL * min_price;
bought = need;
}
}
cout << ans << '\n';
return 0;
}复杂度
时间复杂度是
总结
这题最容易卡住的点是“每次只能买整数升”。
一旦把问题改写成“累计路程至少需要多少总升数”,整数限制就被自然吸收到