跳房子
题目链接:ybt高效进阶5-6-5 / luogu P3957
题目大意
有一些点在一条直线上,起点在最左边,然后给出除了起点外其他点根起点的距离和这个点的分数。
然后你要从起点开始,每次都要跳到一个点上,最后你跳过的点的分数和就是你的总分数。
然后初始你只能跳 d 个,你可以花费 x 点费用使得它可以跳的长度范围变成 [max(1,d-x),d+x]。
然后问你要总分数不小于某个值最小要花费多少,如果无论如何都达不到分数就输出 -1。
思路
首先我们发现如果你枚举费用,它就变成了一个单调队列优化 DP。
大概是你先搞出范围,然后每次先看有没有新的可以转移的点,然后再看原来可以转移的点是否不能转移了。
然后你发现它答案满足单调性, 那就直接二分费用,就可以了。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long
using namespace std;
int n, sta[500001];
ll f[500001], dis[500001], a[500001], d, k;
bool ck(ll mid) {
ll minn = max(1ll, d - mid), maxn = d + mid;
memset(f, -0x7f, sizeof(f));
int l = 1, r = 0, now = 0;
f[0] = 0;
for (int i = 1; i <= n; i++) {
while (dis[now] + minn <= dis[i]) {//新可以转移的点
while (l <= r && f[sta[r]] < f[now]) r--;
sta[++r] = now;
now++;
}
while (l <= r && dis[i] - dis[sta[l]] > maxn) l++;//原来的点不可以转移了
if (l <= r) f[i] = a[i] + f[sta[l]];//要可以转移才转移
if (f[i] >= k) return 1;//判断是否可以
}
return 0;
}
int main() {
scanf("%d %lld %lld", &n, &d, &k);
for (int i = 1; i <= n; i++) scanf("%lld %lld", &dis[i], &a[i]);
ll l = 0, r = dis[n], ans = -1;
while (l <= r) {//二分
int mid = (l + r) >> 1;
if (ck(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
printf("%lld", ans);
return 0;
}