[HG]走夜路 题解
前言
整个机房就我一个人在想动态规划。
想了半天发现一堆性质,结果由于DP中出现折线挂了。
题目描述
某NOIP普及组原题加强版。
\(Jim\) 非常怕黑,他有一个手电筒,设手电筒的电量上限为 \(T\) 。
\(Jim\) 回家的路上有 \((N + 1)\) 个充电站, \(0\) 是起点 \(N\) 是终点,
\(Jim\) 每走一个单位距离消耗一个单位的电量。
给出每个充电站到下一个充电站的距离 \(D\) ,以及冲单位电量的花费 \(P\) ,求整个旅途的最少花费。
P.S. 如果 \(Jim\) 无法保证全程 手电筒都亮着输出 \(-1\) 。
题解
有一个美妙的贪心。
对于当前 \(Jim\) 所在的点,
如果能走到 \(P\) 值比它小的充电站,就适量地充电并走到它,使走到第一个 \(P\) 值小于他的充电站时花费的钱最小;
如果走不到或找不到这样一个点就充满电,走到下一个充电站。
那么怎么找到第一个 \(P\) 值小于它的点呢,简单第使用二分加ST表即可。
时间复杂度为 \(\Theta(n\ logn)\)。
代码
#include <cstdio>
#define int long long
int st[500005][26];
int lg2[500005];
int d[500005], p[500005];
#define min(a,b) ((a<b)?a:b)
void init(int n){
lg2[1] = 0, lg2[2] = 1;
for (int i = 3; i <= n; ++i)
lg2[i] = lg2[i >> 1] + 1;
for (int i = 1; i <= n; ++i)
st[i][0] = p[i];
for (int i = 1; i <= 25; ++i){
int lim = n - (1 << i) + 1;
for (int j = 1; j <= lim; ++j)
st[j][i] = min(st[j][i - 1], st[j + (1 << i - 1)][i - 1]);
}
}
inline int query(int l, int r){
int i = lg2[r - l + 1];
return min(st[l][i], st[r - (1 << i) + 1][i]);
}
#define max(a,b) ((a>b)?a:b)
signed main(){
int n, t; scanf("%lld %lld", &n, &t);
for (int i = 1; i <= n; ++i)
scanf("%lld %lld", &d[i], &p[i - 1]), d[i] += d[i - 1];
init(n);
int pos = 0, eng = 0, cst = 0;
while (pos < n){
if (eng < 0){
puts("-1");
return 0;
}
int l = pos + 1, r = n, res = -1;
while (l <= r){
int mid = l + r >> 1;
if (query(pos + 1, mid) <= p[pos]) r = mid - 1, res = mid;
else l = mid + 1;
}
if (d[res] - d[pos] > t){
cst += (t - eng) * p[pos];
eng = t - (d[pos + 1] - d[pos]);
pos++;
}
else{
int tag = 0;
if (eng > d[res] - d[pos]) tag = eng - d[res] + d[pos];
cst += (d[res] - d[pos] - min(eng, d[res] - d[pos])) * p[pos];
eng = tag; pos = res;
}
}
printf("%lld", cst);
return 0;
}