题目: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
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;
}