题目大意
给一个序列 a i a_i ai,对每一个i,求出最小的非负整数p,使得对任意j满足 a j ≤ a i + p − ∣ i − j ∣ a_j\leq a_i+p-\sqrt {|i-j|} aj≤ai+p−∣i−j∣
题解
移项得
p
≥
a
j
−
a
i
+
∣
i
−
j
∣
p\geq a_j-a_i+\sqrt {|i-j|}
p≥aj−ai+∣i−j∣
绝对值可以去掉,正着算一遍,倒着算一遍,取最大值即可
此时保证i>j
因为
y
=
x
y=\sqrt x
y=x的增长率是越来越慢的,所以x越大,增长率越慢
如果
a
j
+
i
−
j
>
a
k
+
i
−
k
a_j+\sqrt {i-j}>a_k+\sqrt{i-k}
aj+i−j>ak+i−k且
j
>
k
j>k
j>k,则当i增大时,j一定还是比k更优(
i
−
j
\sqrt {i-j}
i−j的增长率比
i
−
k
\sqrt {i-k}
i−k大)
所以
p
i
p_i
pi如果从j转移过来,
p
i
+
1
p_{i+1}
pi+1那么一定不可能从k转移过来,于是我们可以维护一个单调队列,每次用队头更新i即可。
对队列中每个位置,算出它能更新的区间,如果j第一次转移到i,则此时j能更新的区间为[i,n],直到找到下一个x,且x在更新[y,n]时比j优,则把j的区间改为[i,y-1]。
当计算出新的i时,用i更新队尾的区间左端点,如果i更优,则弹掉队尾
将i插入队尾时,需要通过二分找到i能更新的左端点。
代码
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=500005;
template<class T>
void Read(T &x)
{
char c;
bool f=0;
while(c=getchar(),c!=EOF)
{
if(c=='-')
f=1;
else if(c>='0'&&c<='9')
{
x=c-'0';
while(c=getchar(),c>='0'&&c<='9')
x=x*10+c-'0';
if(f)
x=-x;
return;
}
}
}
struct data
{
int j;
int l,r;
data(){}
data(int j,int l,int r):j(j),l(l),r(r){}
};
int n,A[MAXN],ans[MAXN];
int hd,tl;
data Q[MAXN];
//(j<i) p>=a[j]-a[i]+sqrt(i-j)
double val(int i,int j)
{return A[j]+sqrt(i-j);}
void DP()
{
hd=tl=1;
Q[1]=data(1,2,n);
for(int i=2;i<=n;i++)
{
while(hd<=tl&&Q[hd].r<i)
hd++;
Q[hd].l=i;
ans[i]=max(ans[i],(int)ceil(val(i,Q[hd].j)-A[i]));
while(hd<=tl&&val(Q[tl].l,Q[tl].j)<val(Q[tl].l,i))
tl--;
if(hd>tl)
Q[++tl]=data(i,i,n);
else
{
int L=Q[tl].l,R=Q[tl].r,res=Q[tl].r+1;
while(L<=R)
{
int mid=(L+R)/2;
if(val(mid,Q[tl].j)<val(mid,i))
res=mid,R=mid-1;
else
L=mid+1;
}
if(res!=Q[tl].l)
Q[tl].r=res-1;
else
tl--;
if(res<=n)
Q[++tl]=data(i,res,n);
}
}
}
int main()
{
Read(n);
for(int i=1;i<=n;i++)
Read(A[i]);
DP();
reverse(A+1,A+n+1);
reverse(ans+1,ans+n+1);
DP();
reverse(ans+1,ans+n+1);
for(int i=1;i<=n;i++)
printf("%d\n",ans[i]);
return 0;
}