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\n"; #else const char LL[]="%lld\n"; #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; }