题目描述
难度:[蓝]提高+/省选-
输入n(1≤n≤5×105)、d(1≤d≤2000)、k(1≤k≤109)和n个pair,每个pair输入两个数xi(1≤xi≤109)和si(−105≤si≤105)。保证xi是递增的。
坐标轴上有n个点,xi是点的位置,si是到达这个点可以得到的分数。
在游戏的开始,你可以花费g个金币强化你的跳跃能力,使你可以从x跳到在[x+max(d−g,1),x+d+g]中的点。
注意在游戏的过程中,不能花费金币强化能力。注意你必须跳到输入的n个点中,不能跳到其他位置。你从原点0开始向右跳。你可以在任意时刻结束游戏。
目标是让总得分≥k。输出g的最小值。如果无法做到,输出−1。
输入样例1
7 4 10
2 6
5 -3
10 3
11 -3
13 1
17 6
20 2
输出样例1
2
输入样例2
7 4 20
2 6
5 -3
10 3
11 -3
13 1
17 6
20 2
输出样例2
-1
算法
二分答案+单调队列优化DP
比较容易发现单调性,因为g越大,意味着每次跳跃能够选择的下一步范围就越大,这样就更有可能得到更大的分数,因此我们可以二分这个最佳的g。对于一个给定的g,DP
求能够得到的最大分数。
- 只要这个分数≥k,那这个g就是可以的,记录答案并往左搜索看能不能更小;
- 否则g就太小了,往右搜索更大的g。
状态定义
对于一个给定的g,从前往后i∈[1,n]进行DP
。dp[i]表示从0跳到i的最大得分,那只要maxi∈[1,n]dp[i]≥k就说明当前的g可以。
状态转移
在当前位置为i的情况下,它的上一个位置可能是[x−d−g,x−max(d−g,1)],发现这是一个固定长度的窗口,本质上是一个区间去最大值的问题,状态转移方程为dp[i]=maxx−d−g≤x[j]≤x−max(d−g,1)dp[j]+s[i]。
这里最开始我想到的是线段树存dp,O(log2n)求区间最值,但是这样会使得整个算法的时间复杂度为O(log2Unlog2n),其中U是x的值域,在这个数据范围下莽不过去。但是还有一种比较经典的区间最值求法——单调队列,这样时间复杂度被优化至O(nlog2U),就可以过了。
复杂度分析
时间复杂度
二分答案在x的值域上进行,时间复杂度为O(log2U);对于每个g,需要用单调队列来优化DP
,时间复杂度为O(n)。因此,整个算法的时间复杂度为O(nlog2U)。
空间复杂度
除了输入的各种数组,空间瓶颈在于DP
数组dp,以及单调队列的那个队列。空间复杂度取决于状态数量,而状态数量是O(n)级别,因此整个算法的额外空间复杂度为O(n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 500010;
int n, d, k, x[N], s[N];
LL dp[N];
bool check(int g) {
for(int i = 0; i <= n; i++) {
dp[i] = 0;
}
LL mx = -1e15;
deque<int> q;
for(int i = 1, high = 0; i <= n; i++) {
int L = x[i] - d - g, R = x[i] - max(d - g, 1);
while(high <= n && x[high] <= R) {
while(!q.empty() && dp[q.back()] < dp[high]) {
q.pop_back();
}
q.push_back(high++);
}
while(!q.empty() && x[q.front()] < L) {
q.pop_front();
}
if(!q.empty()) {
dp[i] = s[i] + dp[q.front()];
}else {
dp[i] = -1e15;
}
if(dp[i] > mx) {
mx = dp[i];
}
}
return mx >= k;
}
int main() {
scanf("%d%d%d", &n, &d, &k);
for(int i = 1; i <= n; i++) {
scanf("%d%d", &x[i], &s[i]);
}
int l = 0, r = x[n], ans = -1;
while(l <= r) {
int mid = l + r >> 1;
if(check(mid)) {
ans = mid;
r = mid - 1;
}else {
l = mid + 1;
}
}
printf("%d\n", ans);
return 0;
}