您的位置:首页 > 其它

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

2015-10-02 20:11 337 查看

前言

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

题目

你有一支由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..xnx1,x2,x3..xn,和三个数a,b,ca,b,c。让你分组,且每次分组的价值是(当从l到r分开)c+(∑ri=lxi)2∗a+b∗(∑ri=lxi)c+\left(\sum_{i=l}^{r}xi\right)^2*a+b*\left(\sum_{i=l}^{r}xi\right),然后让你求分组的最大值。

数据范围

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[i]=max(f[j]+a*(sum[i]-sum[j])^2+b*(sum[i]-sum[j])+c)然后我们发现这个方程不能用线段树,树状数组来进行维护f[j]f[j]的值的原因是因为这个方程不仅与f[j]f[j]有关,而且和ii也有一定的影响,那么怎么办呢?这时我们便引来了新的解决办法——斜率大法!

好了,现在开始最重要的一步了——化简:

f[i]=max(f[j]+a∗(sum[i]−sum[j])2+b∗(sum[i]−sum[j])+c)f[i]=max(f[j]+a*(sum[i]-sum[j])^2+b*(sum[i]-sum[j])+c)

那么我们转化成两个抉择的优劣:f[j],f[k]f[j],f[k]

若我们假设j>kj>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)(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]2∗a+sum[j]2∗a−2∗sum[i]∗sum[j]∗a+b∗sum[i]−b∗sum[j]>f[k]+sum[i]2∗a+sum[k]2∗a−2∗sum[i]∗sum[k]∗a+b∗sum[i]−b∗sum[k](2)f[j]+sum[i]^2*a+sum[j]^2*a-2*sum[i]*sum[j]*a+b*sum[i]-b*sum[j]>f[k]+sum[i]^2*a+sum[k]^2*a-2*sum[i]*sum[k]*a+b*sum[i]-b*sum[k]

(3)(3)这一步是把重复项给去掉

f[j]+sum[j]2∗a−2∗sum[i]∗sum[j]∗a−b∗sum[j]>f[k]+sum[k]2∗a−2∗sum[i]∗sum[k]∗a−b∗sum[k]f[j]+sum[j]^2*a-2*sum[i]*sum[j]*a-b*sum[j]>f[k]+sum[k]^2*a-2*sum[i]*sum[k]*a-b*sum[k]

(4)(4)移项把j,kj,k移到左边,把关于ii的移到右边

f[j]−f[k]+sum[j]2∗a−sum[k]2∗a−b∗sum[j]+b∗sum[k]>2∗sum[i]∗(sum[j]−sum[k])∗af[j]-f[k]+sum[j]^2*a-sum[k]^2*a-b*sum[j]+b*sum[k]>2*sum[i]*(sum[j]-sum[k])*a

这时便是最重要的一步了,因为你可能一不小心就会移错,最后导致全军覆没:按照正常来说要把a∗2∗(sum[j]−sum[k])a*2*(sum[j]-sum[k])移过去。但是,你要考虑aa和(sum[j]−sum[k])(sum[j]-sum[k])的取值范围,若是小于0就要变号。所以,aa的取值范围是一直都是负数,那么(sum[j]−sum[k])(sum[j]-sum[k])呢?因为前文又设过j>kj>k,且xi>1xi>1所以可以放心的移项了。

最后就是要注意在算和拆项时要认真仔细!

[code]#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1000005;
int g
,l,r,i,n;
long long f
,a,b,c,sum
;
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
);
}


若有错误请各位神犇多多指教,不喜勿喷,O(∩_∩)O谢谢。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: