题目:luogu3957.
题目大意:现在给定一串长度为
n
n
n的序列
a
a
a,
a
[
i
]
a[i]
a[i]表示第
i
i
i个元素的值.现在给定一个值
d
d
d,表示当使用
g
g
g枚金币时,可以每次向右移动
d
+
g
d+g
d+g~
d
+
g
d+g
d+g格(具体看题目),现在问你最少要用多少金币才能使跳到的格子的元素和至少为
k
k
k,若无解输出
−
1
-1
−1.
1
≤
n
≤
5
∗
1
0
5
,
1
≤
d
≤
2000
,
1\leq n\leq 5*10^5,1\leq d\leq 2000,
1≤n≤5∗105,1≤d≤2000,.
这道题我们先来考虑无解的情况,若所有正数元素和都没有 k k k大,则输出 − 1 -1 −1.
否则,我们进行暴力枚举 g g g,每次判定它是否能够使金币数大于 k k k.
很容易发现若 g g g可行,则 g + 1 , g + 2 , . . . , n g+1,g+2,...,n g+1,g+2,...,n就都可行,而如果 g g g不行,则 g − 1 , g − 2 , . . . , 1 g-1,g-2,...,1 g−1,g−2,...,1则都不行,这就满足了二分所需的单调性,所以考虑二分 g g g.
考虑用DP来判定,设
f
[
i
]
f[i]
f[i]表示要跳到第
i
i
i个格子所需的最少金币数.那么方程如下:
f
[
i
]
=
(
m
a
x
x
[
i
]
−
d
−
g
≤
x
[
j
]
≤
m
i
n
(
x
[
i
]
−
d
+
g
,
x
[
i
]
−
1
)
f
[
j
]
)
+
s
[
i
]
f[i]=(max_{x[i]-d-g\leq x[j]\leq min(x[i]-d+g,x[i]-1)}f[j])+s[i]
f[i]=(maxx[i]−d−g≤x[j]≤min(x[i]−d+g,x[i]−1)f[j])+s[i]
发现这个DP貌似是求一个不断往右移的区间的最值的,貌似我们可以用什么东西维护?
我们选用单调队列来维护这个东西,就可以 O ( n ) O(n) O(n)DP了,总时间复杂度 O ( n log n ) O(n\log n) O(nlogn).
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=500000;
const int INF=(1<<30)-1;
int n,d,x[N+9],ans,s[N+9],f[N+9],k,h,t,q[N+9];
void clear(){h=1;t=0;}
bool empty(){return h>t;}
int top(){return q[h];}
void push(int x){
while (h<=t&&f[x]>f[q[t]]) t--;
q[++t]=x;
}
void pop(){h++;}
bool check(int g){
for (int i=1;i<=n;i++) f[i]=-INF;
clear();f[0]=0;
int j=0;
for (int i=1;i<=n;i++){
while (j<i&&x[i]-x[j]>=d-g) push(j++);
while (!empty()&&x[i]-x[top()]>d+g) pop();
if (empty()||f[top()]==-INF) continue;
f[i]=f[top()]+s[i];
if (f[i]>=k) return true;
}
return false;
}
Abigail into(){
scanf("%d%d%d",&n,&d,&k);
for (int i=1;i<=n;i++)
scanf("%d%d",&x[i],&s[i]);
}
Abigail work(){
ans=INF;
for (int i=29;i>=0;i--)
if (check(ans-(1<<i))) ans-=1<<i;
}
Abigail outo(){
if (ans==INF) printf("%d\n",-1);
else printf("%d\n",ans);
}
int main(){
into();
work();
outo();
return 0;
}