题目描述
你有一支由n名预备役士兵组成的部队,士兵从1到n编号,
要将他们拆分成若干特别行动队调入战场。
出于默契的考虑,同一支特别行动队中队员的编号应该连续,
即为形如(i, i + 1, …, i + k)的序列。
编号为i的士兵的初始战斗力为xi ,一支特别行动队的初始战斗力x为队内士兵初始战斗力之和,
即x = xi + xi+1 + … + xi+k。
通过长期的观察,你总结出一支特别行动队的初始战斗力x将按如下经验公式修正为
x':x' = ax^2 + 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,表示士兵的总数。
第二行包含三个整数a, b, c,经验公式中各项的系数。
第三行包含n个用空格分隔的整数x1, x2, …, xn,
分别表示编号为1, 2, …, n的士兵的初始战斗力。
【数据范围】
20%的数据中,n ≤ 1000;50%的数据中,n ≤ 10,000;
100%的数据中,1 ≤ n ≤ 1,000,000,–5 ≤ a ≤ –1,|b| ≤ 10,000,000,
|c| ≤ 10,000,000,1 ≤ xi ≤ 100。
输出格式
输出一个整数,表示所有特别行动队修正后战斗力之和的最大值。
样例输入
4
-1 10 -20
2 2 3 4
样例输出
9
这是一个斜率优化的题
朴素的可以想到有
dp[i] = dp[j] + a*SQR(s[i]-s[j])+b*(s[i]-s[j])+c
对于dp[i]有两个决策j,k,且k更优,则有
dp[k]-dp[j]-2*s[i]*s[k]+2*s[i]*s[j]+a*s[k]^2-a*s[j]^2-b*s[k]+b*s[j]>0
整理得
(dp[k]+a*s[k]^2-b*s[k]) – (dp[j]+a*s[j]^2-b*s[j]) > 2*a*s[i]*(s[k]-s[j])
设函数F(i) = dp[i]+a*s[i]^2-b*s[i]
X(i,j)=(F(k) – F(j)) / (s[k] – s[j])
这就是斜率式了,后面就是维护一个单调队列就好了
第一次写(抄)斜率优化,看了半天定义,以前寥寥草草知道一点队尾的操作,不知道队头还有操作
下面抄一段证明。。。。
现在从左到右,还是设k<j<i,如果g[i,j]<g[j,k],那么j点便永远不可能成为最优解,可以直接将它踢出我们的最优解集。为什么呢?
我们假设g[i,j]<sum[i],那么就是说i点要比j点优,排除j点。
如果g[i,j]>=sum[i],那么j点此时是比i点要更优,但是同时g[j,k]>g[i,j]>sum[i]。这说明还有k点会比j点更优,同样排除j点。
排除多余的点,这便是一种优化!
接下来看看如何找最优解。
设k<j<i。
由于我们排除了g[i,j]<g[j,k]的情况,所以整个有效点集呈现一种上凸性质,即k j的斜率要大于j i的斜率。
这样,从左到右,斜率之间就是单调递减的了。当我们的最优解取得在j点的时候,那么k点不可能再取得比j点更优的解了,于是k点也可以排除。换句话说,j点之前的点全部不可能再比j点更优了,可以全部从解集中排除。
于是对于这题我们对于斜率优化做法可以总结如下:
1,用一个单调队列来维护解集。
2,假设队列中从头到尾已经有元素a b c。那么当d要入队的时候,我们维护队列的上凸性质,即如果g[d,c]<g[c,b],那么就将c点删除。直到找到g[d,x]>=g[x,y]为止,并将d点加入在该位置中。
3,求解时候,从队头开始,如果已有元素a b c,当i点要求解时,如果g[b,a]<sum[i],那么说明b点比a点更优,a点可以排除,于是a出队。最后dp[i]=getDp(q[head])。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
#define SQR(x) (x) * (x)
typedef long long LL;
LL n, a, b, c, s[1000010], q[1000010], data[1000010];
inline double F(int k){return data[k] + a * SQR(s[k]) - b * s[k];}
inline double X(int k, int j){return (F(k) - F(j)) / (s[k] - s[j]);}
int main()
{
int i;
cin>>n>>a>>b>>c;
s[0] = 0;
for(i = 1; i <= n; i++){cin>>s[i]; s[i] += s[i - 1];}
int head = 0, tail = 0; q[0] = data[0] = 0;//习惯于这么弄初始状态。
for(i = 1; i <= n; i++)
{
int p = 2 * a * s[i];
while(head < tail && X(q[head], q[head + 1]) > p) ++head;
int xx = q[head];
data[i] = data[xx] + a * SQR(s[i] - s[xx]) + b * (s[i] - s[xx]) + c;
while(head < tail && X(q[tail], i) > X(q[tail - 1], q[tail])) --tail;
q[++tail] = i;
}
cout<<data[n];
return 0;
}