「APIO2010」 特别行动队 - 斜率优化Dp

题目描述

你有一支由 n n n名预备役士兵组成的部队,士兵从1到 n n n编号,要将他们拆分成若干特别行动队调入战场。出于默契考虑,同一支特别行动队中队员的编号应该连续,即为形如 ( i , i + 1 , . . . , i + k ) (i,i+1,...,i+k) (i,i+1,...,i+k)的序列。

编号为 i i i的士兵的初始战斗力为xi,一支特别运动队的初始战斗力 x x x为队内士兵初始战斗力之和,即 x = x i + x i + 1 + . . . + x i + k x=x_i+x_{i+1}+...+x_{i+k} x=xi+xi+1+...+xi+k

通过长期的观察,你总结出一支特别行动队的初始战斗力x将按如下经验公式修正为 x ′ x&#x27; x x ′ = a x 2 + b x + c x&#x27;=ax^2+bx+c x=ax2+bx+c,其中 a , b , c a,b,c a,b,c是已知的系数 ( a &lt; 0 ) (a&lt;0) (a<0)

作为部队统帅,现在你要为这支部队进行编队,使得所有特别行动队修正后战斗力之和最大。试求出这个最大和。

例如,你有4名士兵, x 1 = 2 , x 2 = 2 , x 3 = 3 , x 4 = 4 x_1=2,x_2=2,x_3=3,x_4=4 x1=2,x2=2,x3=3,x4=4。经验公式中的参数为 a = − 1 , b = 10 , c = − 20 a=-1,b=10,c=-20 a=1,b=10,c=20。此时,最佳方案是将士兵组成3个特别行动队:第一队包含士兵1和士兵2,第二队包含士兵3,第三队包含士兵4。特别行动队的初始战斗力分别为4,3,4,修正后的战斗力分别为4,1,4。修正后的战斗力和为9,没有其它方案能使修正后的战斗力和更大。

分析

根据题意,可以容易得到朴素的状态转移方程:

f [ i ] = max ⁡ 0 ≤ j &lt; i { f [ j ] + a × ( s u m [ i ] − s u m [ j ] ) 2 + b × ( s u m [ i ] − s u m [ j ] ) + c } f[i]=\max_{0\le j&lt;i}\{f[j]+a\times(sum[i]-sum[j])^2+b\times(sum[i]-sum[j])+c\} f[i]=0j<imax{f[j]+a×(sum[i]sum[j])2+b×(sum[i]sum[j])+c}

其中 s u m sum sum表示前缀和。将其展开,会得到 i , j i,j i,j的乘积项,因此想到斜率优化Dp。

对于j,k,若在j处的决策比k处的更优,则:

f [ j ] + a × ( s u m [ i ] − s u m [ j ] ) 2 + b × ( s u m [ i ] − s u m [ j ] ) + c &gt; f [ k ] + a × ( s u m [ i ] − s u m [ k ] ) 2 + b × ( s u m [ i ] − s u m [ k ] ) + c \begin{matrix} f[j]+a\times(sum[i]-sum[j])^2+b\times(sum[i]-sum[j])+c&gt;\\ f[k]+a\times(sum[i]-sum[k])^2+b\times(sum[i]-sum[k])+c \end{matrix} f[j]+a×(sum[i]sum[j])2+b×(sum[i]sum[j])+c>f[k]+a×(sum[i]sum[k])2+b×(sum[i]sum[k])+c

化简可得

f [ j ] − f [ k ] + a × ( s u m [ j ] 2 − s u m [ k ] 2 ) + b × ( s u m [ k ] − s u m [ j ] ) &gt; 2 × a × s u m [ i ] × ( s u m [ j ] − s u m [ k ] ) f[j]-f[k]+a\times(sum[j]^2-sum[k]^2)+b\times(sum[k]-sum[j])&gt;2\times a\times sum[i]\times(sum[j]-sum[k]) f[j]f[k]+a×(sum[j]2sum[k]2)+b×(sum[k]sum[j])>2×a×sum[i]×(sum[j]sum[k])

所以维护一个单调队列,每次将队首和队首+1进行比较,若不满足上式,则将队首出队,直到满足,此时即为最优的决策。再更新当前的最优值。然后将队尾-1与队尾和队尾与 i i i进行比较,若不满足,则队尾出队,直到满足,此时将 i i i加入队列中。最后输出 f [ n ] f[n] f[n]即可。

代码

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#define pp(x) ((x)*(x))
using namespace std;
long long f[1000001],n;
long long a,b,c,sum[1000001];
long long head,tail,q[1000005];
int main() {
	scanf("%lld%lld%lld%lld",&n,&a,&b,&c);
	for (int i=1;i<=n;i++) {
		long long t;
		scanf("%lld",&t);
		sum[i]=sum[i-1]+t;
	}
	memset(f,-0x3f,sizeof(f));
	f[0]=0;
	head=tail=1;
	q[head]=0;
	for (int i=1;i<=n;i++) {
		while (head<tail&&f[q[head]]-f[q[head+1]]+a*(pp(sum[q[head]])-pp(sum[q[head+1]]))+b*(sum[q[head+1]]-sum[q[head]])<=2*a*sum[i]*(sum[q[head]]-sum[q[head+1]])) head++;
		f[i]=f[q[head]]+a*pp(sum[i]-sum[q[head]])+b*(sum[i]-sum[q[head]])+c;
		while (head<tail&&(f[q[tail-1]]-f[q[tail]]+a*(pp(sum[q[tail-1]])-pp(sum[q[tail]]))+b*(sum[q[tail-1]]-sum[q[tail]]))*(sum[q[tail]]-sum[i])<=(f[q[tail]]-f[i]+a*(sum[q[tail]]*sum[q[tail]]-sum[i]*sum[i])+b*(sum[q[tail]]-sum[i]))*(sum[q[tail-1]]-sum[q[tail]])) tail--;
		q[++tail]=i;
	}
	printf("%lld",f[n]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值