bzoj 2216 [Poi2011]Lightning Conductor

题解

给出一个长度为n的序列a,你需要对于每个i,找到最小的整数p满足对于 ∀ j , a j ≤ a i + p − ∣ i − j ∣ \forall j,a_j\leq a_i+p- \sqrt{|i-j|} j,ajai+pij

题意

移项,有 p ≥ a [ j ] − a [ i ] + ∣ i − j ∣ p\geq a[j]-a[i]+\sqrt{|i-j|} pa[j]a[i]+ij
也即是说 p = m a x ( a [ j ] + ∣ i − j ∣ ) − a [ i ] p=max(a[j]+\sqrt{|i-j|})-a[i] p=max(a[j]+ij )a[i]
如果我们正序倒序都做一遍的话,那么就是 p = m a x ( a [ j ] + i − j ) − a [ i ] p=max(a[j]+\sqrt{i-j})-a[i] p=max(a[j]+ij )a[i]
于是奇奇怪怪的东西出现了,决策单调性
观察 i − j \sqrt{i-j} ij ,由于这个本身的增长率是逐渐变小的(可以求导得到),那么也就是说,当转移i的时候,若j优于k,且k<j,那么后面j一定也优于k
也就是说,每个位置作为最优所覆盖的区间一定是连续的
所以我们就可以用一个单调队列来实现,更新那些位置作为最优所覆盖的区间
显然插入一个新点后,它作为最优所替代的区间是[x,n](当然,也可能是空集),我们就一个个弹掉队尾里整体都比它劣的,最后再在队尾里二分
然后倒过来再做一次就行了。记得dp数组也要倒过来。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=500005;
int n;
int a[N];
int q[N],head,tail;
int L[N],R[N];
int d[N];
double calc(int x,int y){
    return a[x]-a[y]+sqrt(abs(x-y));
}
void solve(){
    head=1,tail=0;
    for(int i=1;i<=n;i++){
        while(head<=tail&&R[q[head]]<i)
            head++;
        if(head<=tail){
            L[q[head]]=i;
            d[i]=max(d[i],(int)ceil(calc(q[head],i)));
        }
        if(head>tail||calc(i,n)>calc(q[tail],n)){
            R[i]=n;
            while(head<=tail&&calc(i,L[q[tail]])>=calc(q[tail],L[q[tail]]))
                tail--;
            if(head<=tail){
                int l=L[q[tail]],r=R[q[tail]]+1;
                while(l<r){
                    int mid=(l+r)>>1;
                    if(calc(i,mid)<calc(q[tail],mid))
                        l=mid+1;
                    else
                        r=mid;
                }
                L[i]=l;
                R[q[tail]]=l-1;
            }
        }
        else
            L[i]=i+1;
        q[++tail]=i;
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    solve();
    reverse(a+1,a+n+1);
    reverse(d+1,d+n+1);
    solve();
    reverse(d+1,d+n+1);
    for(int i=1;i<=n;i++)
        printf("%d\n",d[i]);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值