[ 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;
}