[题解/模板]luogu_P3515_LightningConductor(决策单调性

已知一个长度为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\n",(int)(ceil(f[i])));
    
}

猜你喜欢

转载自www.cnblogs.com/superminivan/p/11489572.html