一、题目
已知一个长度为
n
n
n的序列
a
1
,
a
2
,
.
.
.
,
a
n
a1,a2,...,an
a1,a2,...,an。
对于每个
1
<
=
i
<
=
n
1<=i<=n
1<=i<=n,找到最小的非负整数
p
i
pi
pi满足对于任意的
j
j
j,
a
j
≤
a
i
+
p
i
−
∣
i
−
j
∣
aj\leq ai+pi-\sqrt{|i-j|}
aj≤ai+pi−∣i−j∣
1
≤
n
≤
5
×
1
0
5
,
0
≤
a
i
≤
1
0
9
1≤n≤5\times10^5,0≤ai≤10^9
1≤n≤5×105,0≤ai≤109
二、解法
我们先对题目的式子进行改变:
⇒
p
i
>
=
a
j
+
s
q
r
t
∣
i
−
j
∣
−
a
i
\Rightarrow pi>=aj+sqrt{|i-j|}-ai
⇒pi>=aj+sqrt∣i−j∣−ai
我们把题目拆成两个式子,再取最大值:
p
i
>
=
a
j
+
i
−
j
−
a
i
pi>=aj+\sqrt {i-j} -ai
pi>=aj+i−j−ai
p
i
>
=
a
j
+
j
−
i
−
a
i
pi>=aj+\sqrt {j-i}-ai
pi>=aj+j−i−ai
我们考虑第一个式子,第二个式子将之反向即可。
对于每个
j
j
j,把
a
j
+
i
−
j
aj+\sqrt{i-j}
aj+i−j,看成关于
i
i
i的函数
f
j
fj
fj。我们要做的就是在所有
j
≤
i
j≤i
j≤i的函数中找到最值。
我们维护一个决策二分栈,按
j
j
j从大到小维护,对于每一个函数,我们可以求出栈中相邻两个元素,后面的元素刚好超过前面元素的下标
k
k
k(临界点),原因是
s
q
r
t
sqrt
sqrt的增速会越来越慢,当到临界点时,加入的点如果比队尾元素优,那就踢出队尾元素,所以我们维护的是一个
k
k
k单调递减的队列,当处理完队尾后,二分算出当前点的
k
k
k,把当前点加入队列。
对于每个点,我们踢出已不优的队头,再用队头更新答案即可。
#include <cmath>
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
#define cal(i,j) (a[j]+sq[i-j])
const int MAXN = 5e5+5;
int read()
{
int num=0,flag=1;char c;
while((c=getchar())<'0'||c>'9') if(c=='-') flag=-1;
while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
return num*flag;
}
int n,a[MAXN],k[MAXN],q[MAXN];
double p[MAXN],sq[MAXN];
int bound(int x,int y)
{
int l=y,r=k[x]?k[x]:n,mid,ret=r+1;
while(l<=r)
{
mid=(l+r)>>1;
if(cal(mid,x)<=cal(mid,y))
ret=mid,r=mid-1;
else
l=mid+1;
}
return ret;
}
void work()
{
int h=1,t=0;
for(int i=1;i<=n;i++)
{
while(h<t && cal(k[t-1],q[t])<cal(k[t-1],i)) t--;
k[t]=bound(q[t],i),q[++t]=i;
while(h<t && k[h]<=i) ++h;
p[i]=max(p[i],cal(i,q[h]));
}
}
int main()
{
n=read();
for(int i=1;i<=n;i++)
a[i]=read(),sq[i]=sqrt(i);
work();
for(int i=1,j=n;i<j;i++,j--)
swap(a[i],a[j]),swap(p[i],p[j]);
memset(k,0,sizeof k);
work();
for(int i=n;i>=1;i--)
printf("%d\n",max((int)ceil(p[i])-a[i],0));
}