P3515 [POI2011]Lightning Conductor
Captain_Paul
2018-10-09 23:37:47
题意:给出一个序列,对于$i∈[1,n]$,求最小的非负整数$p_i$,使得对于任意的$j∈[1,n]$,都有$a_j<=a_i+p_i-sqrt(|i-j|)$
------------
移项,得$p_i>=a_j-a_i+sqrt(|i-j|)$
所以$p_i=max\left\{a_j+sqrt(|i-j|)\right\}-a_i$
去掉绝对值,得$p_i=max\left\{max\left\{a_j+sqrt(i-j),j∈[1,i-1]\right\},max\left\{a_j+sqrt(j-i),j∈[i+1,n]\right\}\right\}-a_i$
后一种情况只需要前一种情况把序列反转一下就好了
只讨论$j<i$时的做法
这明显是一个可以决策单调性优化的式子
对于每个$j$,把$a_j+sqrt(i-j)$看成一个关于$i$的函数$f_j$
因为$sqrt$的增速是递减的
所以有可能会有较大的$j$在某个位置函数值超过较小的$j$
用单调队列按$j$从小到大来维护这些函数
两个相邻的函数会有一个交点$k_{j,j+1}$
需要满足$k$是严格递增的
如果$k_{j,j+1}>=k_{j+1,j+2}$,
那么$f_{j+1}$其实是没有用到的
从$1$到$n$枚举$i$,考虑怎样把$f_i$加入队列
设队尾的决策为$t$,当$k_{t-1,t}>=k_{t,i}$时,有$f_t(k_{t-1,t})<=f_i(k_{t-1,t})$,$t$就没有用了,出队
然后把$i$加入队列
检查一下队首,如果$k_{h,h+1}<=i$,那么$h$也没用了,出队
这样队首就是所有函数中的最大值
最后把序列反转再做一遍就ok了
```
#include<cstdio>
#include<cstring>
#include<cctype>
#include<cmath>
#include<algorithm>
#define reg register
using namespace std;
const int N=5e5+5;
const double eps=1e-8;
int n,a[N],p[N],q[N];
double b[N],f[N];
inline int read()
{
int x=0,w=1;
char c=getchar();
while (!isdigit(c)&&c!='-') c=getchar();
if (c=='-') c=getchar(),w=-1;
while (isdigit(c))
{
x=(x<<1)+(x<<3)+c-'0';
c=getchar();
}
return x*w;
}
inline double calc(int x,int y){return a[y]+b[x-y];}
inline int getpos(int x,int y)
{
int l=y,r=p[x]?p[x]:n,pos=r+1;
while (l<=r)
{
int mid=(l+r)>>1;
if (calc(mid,x)-calc(mid,y)<=eps) pos=mid,r=mid-1;
else l=mid+1;
}
return pos;
}
inline void work()
{
for (reg int i=1,head=1,tail=0;i<=n;i++)
{
while (head<tail&&calc(p[tail-1],q[tail])<calc(p[tail-1],i)) --tail;
p[tail]=getpos(q[tail],i); q[++tail]=i;
while (head<tail&&p[head]<=i) ++head;
f[i]=max(f[i],calc(i,q[head]));
}
}
int main()
{
n=read();
for (reg int i=1;i<=n;i++) a[i]=read(),b[i]=sqrt(i);
work(); reverse(a+1,a+n+1); reverse(f+1,f+n+1);
memset(p,0,sizeof(p)); work();
for (reg int i=n;i;i--) printf("%d\n",max((int)ceil(f[i])-a[i],0));
return 0;
}
```