<斜率优化><单调队列>——1.【APIO2010】特别行动队

来源:互联网 发布:淘宝冲印照片怎么样 编辑:程序博客网 时间:2024/05/21 14:56

前言

斜率大法好!最近发现自己又忘了斜率优化,单调队列。。(原谅我记忆力真的不好…)所以特意来重新打了几道类似的题,这是第一道,特别行动队。好了,谈正题了!

题目

你有一支由n名预备役士兵组成的部队,士兵从1到n编号,要将他们拆分成若干特别行动队调入战场。出于默契的考虑,同一支特别行动队中队员的编号应该连续,即为形如(i, i + 1, …, i + k)的序列。 编号为i的士兵的初始战斗力为xi ,一支特别行动队的初始战斗力x为队内士兵初始战斗力之和,即x = xi + xi+1 + … + xi+k。 通过长期的观察,你总结出一支特别行动队的初始战斗力x将按如下经验公式修正为x’:x’ = ax2 + bx + c,其中a, b, c是已知的系数(a < 0)。 作为部队统帅,现在你要为这支部队进行编队,使得所有特别行动队修正后战斗力之和最大。试求出这个最大和。 例如,你有4名士兵,x1 = 2, x2 = 2, x3 = 3, x4 = 4。经验公式中的参数为a = –1, b = 10, c = –20。此时,最佳方案是将士兵组成3个特别行动队:第一队包含士兵1和士兵2,第二队包含士兵3,第三队包含士兵4。特别行动队的初始战斗力分别为4, 3, 4,修正后的战斗力分别为4, 1, 4。修正后的战斗力和为9,没有其它方案能使修正后的战斗力和更大。

题意

给你n个数x1,x2,x3..xn,和三个数a,b,c。让你分组,且每次分组的价值是(当从l到r分开)c+(ri=lxi)2a+b(ri=lxi),然后让你求分组的最大值。

数据范围

1 ≤ n ≤ 1,000,000,–5 ≤ a ≤ –1,|b| ≤ 10,000,000,|c| ≤ 10,000,000,1 ≤ xi ≤ 100。

分析

一看到数据,就知道是DP+优化,那么如何优化呢?不急,我们先把普通的方程列出来:f[i]=max(f[j]+a(sum[i]sum[j])2+b(sum[i]sum[j])+c)然后我们发现这个方程不能用线段树,树状数组来进行维护f[j]的值的原因是因为这个方程不仅与f[j]有关,而且和i也有一定的影响,那么怎么办呢?这时我们便引来了新的解决办法——斜率大法!
好了,现在开始最重要的一步了——化简:
f[i]=max(f[j]+a(sum[i]sum[j])2+b(sum[i]sum[j])+c)
那么我们转化成两个抉择的优劣:f[j],f[k]
若我们假设j>k,且j的决策比k的决策要优,则
(1)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)
(2)f[j]+sum[i]2a+sum[j]2a2sum[i]sum[j]a+bsum[i]bsum[j]>f[k]+sum[i]2a+sum[k]2a2sum[i]sum[k]a+bsum[i]bsum[k]
(3)这一步是把重复项给去掉
f[j]+sum[j]2a2sum[i]sum[j]absum[j]>f[k]+sum[k]2a2sum[i]sum[k]absum[k]
(4)移项把j,k移到左边,把关于i的移到右边
f[j]f[k]+sum[j]2asum[k]2absum[j]+bsum[k]>2sum[i](sum[j]sum[k])a
这时便是最重要的一步了,因为你可能一不小心就会移错,最后导致全军覆没:按照正常来说要把a2(sum[j]sum[k])移过去。但是,你要考虑a(sum[j]sum[k])的取值范围,若是小于0就要变号。所以,a的取值范围是一直都是负数,那么(sum[j]sum[k])呢?因为前文又设过j>k,且xi>1所以可以放心的移项了。
最后就是要注意在算和拆项时要认真仔细!

#include<iostream>#include<cstdlib>#include<cstdio>#include<cmath>#include<cstring>#include<algorithm>using namespace std;const int N=1000005;int g[N],l,r,i,n;long long f[N],a,b,c,sum[N];double pp;long long make(int x,int y){    return f[x]-f[y]+sum[x]*sum[x]*a-sum[y]*sum[y]*a-b*sum[x]+b*sum[y];}int main (){    scanf("%d",&n);    scanf("%lld%lld%lld", &a, &b, &c);    for (int i=1;i<=n;i++) {scanf("%d",&l);sum[i]=sum[i-1]+l;}    l=1;r=1;    for (int i=1;i<=n;i++){        while ((l<r)&&(make(g[l+1],g[l])>2*a*(sum[g[l+1]]-sum[g[l]])*sum[i])) l++;        f[i]=f[g[l]]+(sum[i]-sum[g[l]])*(sum[i]-sum[g[l]])*a+(sum[i]-sum[g[l]])*b+c;        while ((l<r)&&(make(i,g[r])*(sum[g[r]]-sum[g[r-1]])>make(g[r],g[r-1])*(sum[i]-sum[g[r]]))) r--;        g[++r]=i;    }    printf("%lld",f[n]);}

若有错误请各位神犇多多指教,不喜勿喷,O(∩_∩)O谢谢。

0 0
原创粉丝点击