BZOJ3203 SDOI2013保护出题人(三分)

  给a做一个前缀和,那么现在每次所查询的就是(sn-sk)/(bn+nd-(k+1)d)的最大值。这个式子可以看成是(bn+nd,sn)和((k+1)d,sk)所成直线的斜率。

  脑补一条直线不断减小斜率,容易发现可能成为最大值的点一定在下凸壳上。并且凸壳上的点和该点间的斜率变化情况是一个凸函数。于是维护出凸壳,在凸壳上三分即可。

#include<iostream> 
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 100010
#define ll long long
ll read()
{
    ll x=0,f=1;char c=getchar();
    while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
    while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
    return x*f;
}
int n,q[N],tail;
double ans=0;
ll d,a[N],b[N];
double calc(int x,int y)
{
    return 1.0*(a[y]-a[x])/((y-x)*d);
}
double getans(int n,int k)
{
    return 1.0*(a[n]-a[k])/(b[n]+(n-k-1)*d);
}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("bzoj3203.in","r",stdin);
    freopen("bzoj3203.out","w",stdout);
    const char LL[]="%I64d
";
#else
    const char LL[]="%lld
";
#endif
    n=read(),d=read();
    for (int i=1;i<=n;i++) a[i]=read(),b[i]=read();
    q[tail=1]=0;
    for (int i=1;i<=n;i++)
    {
        a[i]+=a[i-1];
        int l=1,r=tail;
        while (l+2<r)
        {
            int mid=l+r>>1;
            double x=getans(i,q[mid]),y=getans(i,q[mid+1]);
            if (x>y) r=mid+1;else l=mid;
        }
        int mx=l;
        if (l+1<=r&&getans(i,q[l+1])>getans(i,q[mx])) mx=l+1;
        if (l+2<=r&&getans(i,q[l+2])>getans(i,q[mx])) mx=l+2;
        ans+=getans(i,q[mx]);
        while (tail>=2&&calc(q[tail-1],q[tail])>calc(q[tail],i)) tail--;
        q[++tail]=i;
    }
    printf("%0.lf",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/Gloid/p/9695375.html