【传送门:BZOJ1911】
简要题意:
有n个人,每个人都有一个战力值,将这n个人分成若干个段(每个段内的人的编号都是连续的),每个段的初始战力值为每个段内的人的战力值的总和
给出常数a,b,c,而每个段的真正战力值为ax2+bx+c(x为这个段的初始战力值),求出分成若干个段得到的所有段的最大真正战力值总和
题解:
DP很容易想到
设f[i]为将前i个人分成若干段的最大真正战力值,s[i]为前i个人战力值总和
可以得到:f[i]=max(f[j]+(s[i]-s[j])*(s[i]-s[j])*a+(s[i]-s[j])*b+c)
但这道题数据极大
所以用斜率优化DP
设j1<j2<i
f[j2]+(s[i]-s[j2])*(s[i]-s[j2])*a+(s[i]-s[j2])*b+c>f[j1]+(s[i]-s[j1])*(s[i]-s[j1])*a+(s[i]-s[j1])*b+c
化简得到:(f[j2]-f[j1]+(s[j2]*s[j2]-s[j1]*s[j1])*a-(s[j2]-s[j1])*b)/(s[j2]-s[j1])>2*a*s[i]
然后做斜率优化就可以了
注意要加long long
来自蒟蒻的吐槽:这道题!!我其实应该在n久之前就应该A了,结果在斜率优化的时候把slop(list[head],list[head+1])错手打成了slop(list[head],list[head]+1),痛心疾首
参考代码:
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; typedef long long LL; LL a,b,c; LL s[1100000]; LL f[1100000]; /* f[i]=max(f[j]+(s[i]-s[j])*(s[i]-s[j])*a+(s[i]-s[j])*b+c) f[i]=max(f[j]+(s[i]-s[j])*(s[i]-s[j])*a+(s[i]-s[j])*b)+c j1<j2<i f[j2]+(s[i]-s[j2])*(s[i]-s[j2])*a+(s[i]-s[j2])*b > f[j1]+(s[i]-s[j1])*(s[i]-s[j1])*a+(s[i]-s[j1])*b f[j2]+s[i]*s[i]*a-2*a*s[i]*s[j2]+s[j2]*s[j2]*a+s[i]*b-s[j2]*b > f[j1]+s[i]*s[i]*a-2*a*s[i]*s[j1]+s[j1]*s[j1]*a+s[i]*b-s[j1]*b f[j2]-2*a*s[i]*s[j2]+s[j2]*s[j2]*a-s[j2]*b > f[j1]-2*a*s[i]*s[j1]+s[j1]*s[j1]*a-s[j1]*b (f[j2]-f[j1]+(s[j2]*s[j2]-s[j1]*s[j1])*a-(s[j2]-s[j1])*b)/(s[j2]-s[j1]) >2*a*s[i] */ LL slop(int j1,int j2) { return (f[j2]-f[j1]+(s[j2]*s[j2]-s[j1]*s[j1])*a-(s[j2]-s[j1])*b)/(s[j2]-s[j1]); } int list[1100000]; int main() { int n; scanf("%d",&n); scanf("%lld%lld%lld",&a,&b,&c); s[0]=0LL; for(int i=1;i<=n;i++) { LL x; scanf("%lld",&x); s[i]=s[i-1]+x; } int head=1,tail=1;list[1]=0; for(int i=1;i<=n;i++) { while(head<tail&&slop(list[head],list[head+1])>2LL*a*s[i]) head++; int j=list[head]; f[i]=f[j]+(s[i]-s[j])*(s[i]-s[j])*a+(s[i]-s[j])*b+c; while(head<tail&&slop(list[tail-1],list[tail])<slop(list[tail],i)) tail--; list[++tail]=i; } printf("%lld\n",f[n]); return 0; }