难度
提 高 + / 省 选 − \color{cyan}提高+/省选- 提高+/省选−
− 8.02 k 48.38 k -\frac{8.02k}{48.38k} −48.38k8.02k
题意
- 首先给你一个 x x x 坐标轴,开始在原点,正方向处给定 n n n 个点。
- 给定这每个点的坐标 p o s i pos_i posi 以及每个点的收益 s c o i sco_i scoi (可正可负)
- 给你一个弹跳力 d d d 。一开始每次跳跃只能向右移动 d d d 个单位。
- 你花 g g g 块钱,使得弹跳力变成一个区间 S = [ max ( 1 , d − g ) , d + g ] S=[\max(1,d-g) ,d+g] S=[max(1,d−g),d+g]
表示每次跳跃你都可以向右移动一定整数长度,这个长度要在 S S S 区间中。 - 但是你每次必须跳在给定的点中。
问你至少花多少钱,才可以使得某种跳跃方案,你的收益超过 k k k 。
若无法收益超过 k k k ,输出 − 1 -1 −1
数据范围
1 ≤ n ≤ 5 × 1 0 5 1\le n\le 5\times 10^5 1≤n≤5×105
1 ≤ d ≤ 2000 1\le d\le 2000 1≤d≤2000
1 ≤ p o s i , k ≤ 1 0 9 1\le pos_i,k\le 10^9 1≤posi,k≤109
∣ s c o i ∣ < 1 0 5 |sco_i|<10^5 ∣scoi∣<105
思路
1.最开始的思路
首先我们抛开其他的条件,就问你:给定 g g g 块钱,你怎么算此时的收益最大值?
- 每次都可以从一些点跑到另一些点,直接 D P DP DP 不就好了?
- 设 d p ( i ) dp(i) dp(i) 表示走到第 i i i 个点的最大收益,然后列出简单的状态转移方程:
- d p ( i ) = max { d p ( s t ) , d p ( s t + 1 ) , ⋯ , d p ( e d ) } + s c o i dp(i)=\max\{dp(st),dp(st+1),\cdots,dp(ed)\}+sco_i dp(i)=max{ dp(st),dp(st+1),⋯,dp(ed)}+scoi
- 因为你每次走的长度在区间 [ max ( 1 , d − g ) , d + g ] [\max(1,d-g),d+g] [max(1,d−g),d+g] 范围之内,因此转移图肯定是这样的:
- 如果是上面那种转移一推多,时间复杂度就会大幅增加。所以我们选择下面这种。
- 对于每一个 i i i ,易得这一段 [ s t , e d ] [st,ed] [st,ed] 肯定是 像滑动窗口一样向右移动的,这就是这题的突破口。
- 我们每次要求这一段区间的最大值,直接使用单调队列RMQ即可。时间复杂度 O ( N ) O(N) O(N)
为什么不能用 S T ST ST 表?
- S T ST ST 表是静态的。每次你转移都会更新 d p ( i ) dp(i) dp(i),那就无法在时间内实现该效果了。
2.求花费最小?
来,跟我读:
- 求收益最大的最小花费用二分
- 求 x x x 最小时 y y y 最大用二分
- 求 x x x 最大时 y y y 最小用二分
我咋老记不住呢???
对于花费 g g g,明显符合二分性质。我们就二分它呗。
3.具体实现单调队列?
- 这单调队列的代码实现也是让我敲得很费劲。
- 由于我们的状态转移会修改数组的值,我们每次就 c o p y copy copy 一下原 s c o sco sco 数组变成 d p dp dp 数组
- 首先你最开始在原点,即第 0 0 0 个点。状态转移的方程 i i i 肯定是从 1 1 1 到 n n n 的。
- 因为 i i i和滑动窗口的右端点是不对应的,我们还要记录一下滑动窗口的右端点下标 s t st st
继续看图啦!
进队列
- 首先 e d ed ed 能进滑动窗口的条件: i − e d ≥ d 2 i-ed\ge d_2 i−ed≥d2
- 若能进单调队列,然后再按照单调队列的代码,删重
while(Q.size() && dp[Q.back()] <= dp[ed])Q.pop_back();
- 当然也可以每次 c h e c k check check 到一半发现收益大于 k k k 然后返回,我这里没有这么写。
出队列
- 然后,考虑队头是否还在窗口中的条件: i − s t ≤ d 1 i-st\le d_1 i−st≤d1
- 如果不满足,我们直接将这个点给删掉。
状态转移
- 我们如果
Q.size()!=0
满足的,也就是说该点可以通过前面的点转移过来,直接转移 dp[i] = dp[Q.front()] + sc[i];
- 如果
Q.size()==0
也就是说该点不能通过前面的转移过来,那么直接给该点设置一个负无穷大的收益即可。
答案
- 收益最大值就是 max { d p ( 0 ) , d p ( 1 ) , ⋯ , d p ( n ) } \max\{dp(0),dp(1),\cdots,dp(n)\} max{ dp(0),dp(1),⋯,dp(n)}
4. 小优化
- 数据量大,快读。
- 如果所有的收益为正的点全拿了,都无法超过 k k k ,直接输出 − 1 -1 −1
核心代码
时间复杂度: O ( N log N ) O(N\log N) O(NlogN)
空间复杂度: O ( N ) O(N) O(N)
/*
_ __ __ _ _
| | \ \ / / | | (_)
| |__ _ _ \ V /__ _ _ __ | | ___ _
| '_ \| | | | \ // _` | '_ \| | / _ \ |
| |_) | |_| | | | (_| | | | | |___| __/ |
|_.__/ \__, | \_/\__,_|_| |_\_____/\___|_|
__/ |
|___/
*/
const int MAX = 5e5+50;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
ll dis[MAX],sc[MAX];
ll dp[MAX];
deque<ll>Q;
int n,d,k;
ll check(int x){
int d1 = d + x;
int d2 = max(1,d-x);
ll ans = 0;
for(int i = 1;i <= n;++i){
dp[i] = sc[i];
}
while(Q.size())Q.pop_back();
int ed = -1; /// 因为一开始原点的下标为0的点还没有进入队列
dis[0] = 0;
sc[0] = 0;
dp[0] = 0;
for(int i = 1;i <= n;++i){
while(ed + 1 < i && dis[i] - dis[ed+1] >= d2){
/// 进队列
ed++;
while(Q.size() && dp[Q.back()] <= dp[ed])Q.pop_back();
Q.push_back(ed);
}
while(Q.size() && dis[i] - dis[Q.front()] > d1)Q.pop_front(); /// 出队列
if(Q.size()){
/// 状态转移
dp[i] = dp[Q.front()] + sc[i];
ans = max(ans,dp[i]);
}else dp[i] = -LINF;
}
return ans;
}
int main()
{
n = read();
d = read();
k = read_ll();
ll pos = 0;
for(int i = 1;i <= n;++i){
dis[i] = read_ll();
sc[i] = read_ll();
if(sc[i] > 0)pos += sc[i];
}
if(pos < k){
puts("-1");
return 0;
}
int L = 0,R = dis[n];
while(L < R){
int M = L + R >> 1;
if(check(M) >= k)R = M;
else L = M + 1;
}
printf("%d",L);
return 0;
}