题意:在地面上确定一个起点,然后在起点右侧画(n(n<=500000))个格子,这些格子都在同一条直线上.每个格子内有一个数字(整数),表示到达这个 格子能得到的分数.玩家第一次从起点开始向右跳,跳到起点右侧的一个格子内.第二次再从当前位置继续向右跳,依此类推.规则规定:玩家每次都必须跳到当前位置右侧的一个格子内.玩家可以在任意时刻结束游戏,获得的分数为曾经到达过的格子中的数字之和.每一次可以跳的固定距离为(d),然后花费(g)元,可以跳的区间范围为(max(d-g,1),d+g),求最少花费多少钱可以获得至少(k)分?
答案显然具有单调性,所以可以二分最小花费为(mid),得到可以跳的区间范围为([max(d-mid,1),d+mid]).
设(f[i])表示跳到第(i)个格子能够获得的最大分数,(f[i]=f[j]+val[i]),其中((max(d-mid,1)<=dis(i-j)<=d+mid)),(val[i])表示第i格的得分.
这个(j)可以(n^2)枚举(50分),也可以单调队列维护.但是本题的单调队列需要用到双端队列(deque),因为(j)既有上界又有下界.
时间复杂度(O(nlog_{x_n}).)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
int x=0,o=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')o=-1,ch=getchar();
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*o;
}
const int N=500005;
int n,d,k,x[N],val[N];ll sum,f[N];
inline bool check(int mid){
for(int i=0;i<=n;++i)f[i]=-1e18;f[0]=0;
//这里一定要赋值为负无穷,因为格子的分数可能是负的
//没有的话,只有50分
int minn=max(d-mid,1),maxn=d+mid;//跳的上下界
int j=0;deque<int>q;//在里面建立队列就不用初始化了
for(int i=1;i<=n;++i){
while(j<i&&x[i]-x[j]>=minn){
while(q.size()&&f[j]>=f[q.back()])q.pop_back();
q.push_back(j);++j;
}//把符合条件的j加进队列里面,并维护单调性
while(q.size()&&x[i]-x[q.front()]>maxn)q.pop_front();//队列前面也要维护
if(q.size())f[i]=f[q.front()]+val[i];//更新
if(f[i]>=k)return 1;//判断
}
return 0;
/*
暴力的n方做法
for(int i=0;i<=n;++i)f[i]=0;
int minn=max(d-mid,1),maxn=d+mid;
for(int i=1;i<=n;++i){
for(int j=0;j<i;++j){
if(x[i]-x[j]>=minn&&x[i]-x[j]<=maxn)
f[i]=max(f[i],f[j]+val[i]);
}
if(f[i]>=k)return 1;
}
return 0;
*/
}
int main(){
n=read();d=read();k=read();
for(int i=1;i<=n;++i){
x[i]=read();val[i]=read();
if(val[i]>0)sum+=val[i];
}
if(sum<k){puts("-1");return 0;}//特判
int l=0,r=x[n],ans,mid;//最大花费就是最远的那个格子的距离
while(l<=r){
mid=(l+r)>>1;
if(check(mid)){
ans=mid;
r=mid-1;
}
else l=mid+1;
}
printf("%d
",ans);
return 0;
}