【洛谷P3628】特别行动队
题目
题目链接:https://www.luogu.com.cn/problem/P3628
你有一支由 \(n\) 名预备役士兵组成的部队,士兵从 \(1\) 到 \(n\) 编号,你要将他们拆分成若干特别行动队调入战场。出于默契的考虑,同一支特别行动队中队员的编号应该连续,即为形如 \((i, i + 1, \cdots i + k)\)的序列。所有的队员都应该属于且仅属于一支特别行动队。
编号为 \(i\) 的士兵的初始战斗力为 \(x_i\) ,一支特别行动队的初始战斗力 \(X\) 为队内士兵初始战斗力之和,即 \(X = x_i + x_{i+1} + \cdots + x_{i+k}\)。
通过长期的观察,你总结出对于一支初始战斗力为 \(X\) 的特别行动队,其修正战斗力 \(X'= aX^2+bX+c\),其中 \(a,~b,~c\) 是已知的系数(\(a<0\))。 作为部队统帅,现在你要为这支部队进行编队,使得所有特别行动队的修正战斗力之和最大。试求出这个最大和。
\(n\leq 10^6\)。
思路
不记得从哪听说这道题不是很模板?但是感觉还是模板啊QWQ。
设 \(f[i]\) 表示前 \(i\) 个人分好组的最大和。设 \(s[i]=\sum^{i}_{j=1}x[i]\),显然
\[f[i]=\max(f[j]+a(s[i]-s[j])^2+b(s[i]-s[j])+c)
\]
展开,移项后得到
\[f[j]+a·s[j]^2-b·s[j]=2a·s[i]·s[j]+f[i]-a·s[i]^2-b·s[i]+c
\]
那么可以看做一条斜率为 \(2a·s[i]\),截距为 \(f[i]-a·s[i]^2-b·s[i]+c\) 的直线,决策点为 \((s[j],f[j]+a·s[j]^2-b·s[j])\) 的平面直角坐标系。
由于 \(a<0\),那么斜率满足单调递减,同时决策点的横坐标单调递增,所以单调队列维护上凸壳即可。
时间复杂度 \(O(n)\)。
代码
#include <cctype>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=1000010;
ll a,b,c,f[N],s[N],Y[N];
int n,l,r,q[N];
ll read()
{
ll d=0,f=1; char ch=getchar();
while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
while (isdigit(ch)) d=(d<<3)+(d<<1)+ch-48,ch=getchar();
return d*f;
}
double slope(int x,int y)
{
return 1.0*(Y[y]-Y[x])/(s[y]-s[x]);
}
int main()
{
n=read(); a=read(); b=read(); c=read();
for (int i=1;i<=n;i++)
s[i]=s[i-1]+read();
l=r=1;
for (int i=1;i<=n;i++)
{
for (int x=q[l],y=q[l+1];;x=q[l],y=q[l+1])
if (l<r && slope(x,y)>=2.0*a*s[i]) l++;
else break;
int j=q[l];
f[i]=f[j]+a*(s[i]-s[j])*(s[i]-s[j])+b*(s[i]-s[j])+c;
Y[i]=f[i]+a*s[i]*s[i]-b*s[i];
for (int x=q[r-1],y=q[r];;x=q[r-1],y=q[r])
if (l<r && slope(x,y)<=slope(y,i)) r--;
else break;
q[++r]=i;
}
printf("%lld",f[n]);
return 0;
}