1. 程式人生 > >[ APIO 2010 ] 特別行動隊

[ 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],q[hd+1])>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;
}