[ APIO 2010 ] 特别行动队

\(\\\)

Description


  • \(N\le 10^6,–5 ≤ a ≤ –1,|b| ≤ 10^7,|c| ≤ 10^7,1 ≤ x_i ≤ 100\)

\(\\\)

Solution


首先统计出 \(s[n]=\sum_{i=1}^nx_i\)

\(f[i]\) 为前 \(i\) 个人可以达到的最高战斗力,易得转移

\[f[i]=f[j]+a(s[i]-s[j])^2+b(s[i]-s[j])+c \]

列出直线方程的形式

\[f[j]+as[j]^2-bs[j]=f[i]-as[i]^2-bs[i]-c+2as[i]s[j] \]

容易发现抽象的点就是 \((s[j],f[j]+as[j]^2-bs[j])\)

\(\\\)

\(t[i]=as[i]^2-bs[i]\)

当一个状态下 \(k\)\(j\) 优,有

\[f[k]+as[k]^2-bs[k]-2as[i]s[k]> f[j]+as[j]^2-bs[j]-2as[i]s[j] \]

整理,化简得

\[f[k]+t[k]-f[j]-t[j]>2as[i](s[k]-s[j]) \]

\[\frac{f[k]+t[k]-f[j]-t[j]}{s[k]-s[j]}>2as[i] \]

注意分母除过去的时候需要是正数。

注意直线的斜率 \(2as[i]\) 是负数,且不断变小,可以用单调队列维护了。

同样因为直线的斜率为负数,所以我们需要维护上凸包。

\(\\\)

Code


#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define gc getchar
#define R register
#define N 1000010
using namespace std;
typedef long long ll;

inline ll rd(){
  ll x=0; bool f=0; char c=gc();
  while(!isdigit(c)){if(c=='-')f=1;c=gc();}
  while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
  return f?-x:x;
}

ll n,m,a,b,c,s[N],t[N],g[N],q[N],hd,tl,f[N];

inline double calck(ll x,ll y){
  return (double)(f[x]+t[x]-f[y]-t[y])/(double)(s[x]-s[y]);
}

inline ll w(ll x,ll y){
  return f[x]+t[x]+g[y]-2*a*s[x]*s[y];
}

int main(){
  n=rd();
  a=rd(); b=rd(); c=rd();
  for(R ll i=1;i<=n;++i){
    s[i]=s[i-1]+rd();
    t[i]=a*s[i]*s[i]-b*s[i];
    g[i]=a*s[i]*s[i]+b*s[i]+c;
  }
  q[hd=tl=1]=0;
  for(R int i=1;i<=n;++i){
    while(hd<tl&&calck(q[hd+1],q[hd])>2.0*a*s[i]) ++hd;
    f[i]=w(q[hd],i);
    while(hd<tl&&calck(q[tl],q[tl-1])<calck(i,q[tl])) --tl;
    q[++tl]=i;
  }
  printf("%lld\n",f[n]);
  return 0;
}
posted @ 2018-11-13 07:14  SGCollin  阅读(227)  评论(0编辑  收藏  举报