已知一个长度为n的序列a1,a2,...,an。
对于每个1<=i<=n,找到最小的非负整数p满足 对于任意的j, aj < = ai + p - sqrt(abs(i-j))
每个点的$p=max(a[j]-a[i]+sqrt{|i-j|}$,可以想到dp,$f[i]=max(a[j]-a[i]+sqrt{|i-j|}$
绝对值比较常见的做法可以分类讨论去掉,比如我们只处理$j<i$的情况,然后在倒着做一遍就可以了
对于这个根号,整理一下式子啥的好像并不能用斜率优化之类的,不妨考虑决策单调性,
证明:
需证:对于两个决策点$p1,p2$和$i1<i2$,若从$p2$转移到$i1$比从$p1$转移到$i1$优,则从$p2$转移到$i2$也比$p1$优
所以已知$a[p1]-a[i1]+sqrt{p1-i1}<a[p2]-a[i1]+sqrt{p2-i1}$,求证$a[p1]-a[i2]+sqrt{p1-i2}<a[p2]-a[i2]+sqrt{p2-i2}$
发现两个式子化简后只有根号里有$i1,i2$,所以可以从根号下手,注意$i1<i2$,也就是说$p-i1>p-i2$,相当于根号这个函数的自变量变小了一点,并且两边变小的值相同
那么就可以想到关于斜率/导数之类的东西,也就是根号函数斜率递减,在前面和后面自变量变化相同的值,函数值变化有一个大小关系:对于$i<j,sqrt{i-Delta}-sqrt{i}>sqrt{j-Delta}-sqrt{j}$,变化量相同时自变量小的函数值变化量大,那么单调性就显然了
并没写分治做法
二分栈流程:
1.排除队头所有右端点小于$i$的元素,然后把队头元素左端点设为$i$
2.用队头更新$f[i]$
3.排除所有队尾比$i$不优秀的决策
4.若队列空则直接插入$i$,否则:
二分找到转折点$pos$,若$pos$不与队尾左端点重合,修改队尾元素右端点为$pos-1$,否则弹出队尾
若$pos<=n$插入$i$
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; const int maxn=500009; int n,a[maxn]; double f[maxn],g[maxn],sq[maxn]; struct node{ int p,l,r; }q[maxn]; double calc(int j,int i){ return a[j]+sq[i-j]; } int bound(int t,int i){ int pos=q[t].r+1,l=q[t].l,r=q[t].r; while(l<=r){ int mid=l+r>>1; if(calc(q[t].p,mid)<calc(i,mid))pos=mid,r=mid-1; else l=mid+1; } return pos; } void work(){ int head=1,tail=0,pos; for(int i=1;i<=n;i++){ while(head<=tail && q[head].r<i)head++;q[head].l=i; f[i]=max(f[i],calc(q[head].p,i)-a[i]); while(head<=tail && calc(q[tail].p,q[tail].l)<calc(i,q[tail].l))tail--; if(head>tail)q[++tail]=(node){i,i,n}; else{ int pos=bound(tail,i); if(pos!=q[tail].l)q[tail].r=pos-1; else tail--; if(pos<=n)q[++tail]=(node){i,pos,n}; } } } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d",&a[i]),sq[i]=sqrt(i); work(); for(int i=1,j=n;i<j;i++,j--) swap(a[i],a[j]),swap(f[i],f[j]); work(); for(int i=n;i>=1;i--) printf("%d ",(int)(ceil(f[i]))); }