bzoj 2792 [Poi2012]Well 单调队列 二分

二分答案,先求一个满足相邻两项之差不大于答案时每个位置的最大值b[i]。
设当前位置为i,二分的答案为x,那么需要满足对于 j[1,n] b[i]=min(a[j]+|ij|x)
对于 j<i 直接维护最小值,对于 j>i 用单调队列。
然后枚举0的位置,那么受影响区间的左右端点都单调。
求一个前缀和后可以 O(1) 计算答案的增加。

#include 
using namespace std;
#define ll long long
#define N 1100000
int n,h,r;
ll m,sum[N];
int a[N],pos[N],b[N],q[N];
int cmp(int x,int y){return a[x]y];}
int check(int x)
{
    h=1;r=0;
    for(int i=1;i<=n;i++)
    {
        while(r&&a[q[r]]+(ll)q[r]*x>=a[i]+(ll)i*x)
            r--;
        q[++r]=i;
    }
    ll v=0,mn=1ll<<60;
    for(int i=1;i<=n;i++)
    {
        b[i]=a[i];
        mn=min(mn,a[i]-(ll)i*x);
        b[i]=mn+(ll)i*x;
        if(q[h]==i)h++;
        if(h<=r)b[i]=min((ll)b[i],a[q[h]]+(ll)(q[h]-i)*x);
        v+=a[i]-b[i];
    }
    if(v>m)return 0;
    for(int i=1;i<=n;i++)
        sum[i]=sum[i-1]+b[i];
    for(int i=1,l=1,r=1;i<=n;i++)
    {
        while(l*x)
            l++;
        while(r1]>(ll)(r+1-i)*x)
            r++;
        ll t=v+((sum[i]-sum[l-1])-(ll)(i-l)*(i-l+1)/2*x)+
        ((sum[r]-sum[i-1])-(ll)(r-i)*(r-i+1)/2*x)-b[i];
        if(t<=m)return i;
    }
    return 0;
}
int main()
{
    int l=0,r=0;
    scanf("%d%lld",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        r=max(r,a[i]),pos[i]=i;
    }
    sort(pos+1,pos+1+n,cmp);
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(check(mid))r=mid-1;
        else l=mid+1;
    }
    printf("%d %d\n",check(l),l);
    return 0;
}

你可能感兴趣的:(单调队列,二分)