【问题描述】
你有一支由 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的士兵的初始战斗力。
【输出格式】
输出一个整数,表示所有特别行动队修正后战斗力之和的最大值。
【样例输入】
4
-1 10 -20
2 2 3 4
【样例输出】
9
【数据范围】
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。
此题考查斜率优化动态规划。
朴素的转移方程方程很好想出:(若令s数组为前缀和)f[i] = max{f[j] + a·sqr(s[i] - s[j]) + b·(s[i] - s[j]) + c}。
但这样做显然要超时。
一种优化是:发现b项对整个结果并没有影响,所以改进方程为:f[i] = max{f[j] + a·sqr(s[i] - s[j]) + c},最后输出f[n] + b·s[n]即可(注意c不能被分离)。
然后由决策的单调性可知:若令g[i]为i的决策位置,那么有g[1] <= g[2] <= g[3] <= ... <= g[n],(证明略)于是可以缩小枚举范围。
但这样还是不能过完。
于是引入斜率优化。
设i的最佳决策位置为j,k是异于j的一点(k > j),则有:
f[j] + a·sqr(s[i] - s[j]) + c >= f[k] + a·sqr(s[i] - s[k]) + c。
即:f[j] + a·sqr(s[i]) + a·sqr(s[j]) - 2a·s[i]·s[j] >= f[k] + a·sqr(s[i]) + a·sqr(s[k]) - 2a·s[i]·s[k]。
也即:f[j] - f[k] + a·(sqr(s[j]) - sqr(s[k])) >= 2a·s[i]·(s[j] - s[k])。
由s[j] < s[k]又得:
f[j] - f[k] + a·(sqr(s[j]) - sqr(s[k]))
—————————————————————— <= 2a·s[i]
s[j] - s[k]
将不等式左边记作H(j, k),于是可以用H函数判定j是否优于k。
用一个双端队列q维护当前决策序列q1, q2, ...,并且须满足H(q1, q2) >= H(q2, q3) >= ... >= 2a·s[i]。
从队首维护决策序列的规则是:
把队首的不满足H(q[f], q[f + 1])(f为队首指针)<= 2a·s[i]的元素q[f]去掉,去掉过后的q[f]即为当前最优决策。
从队尾维护决策序列的规则是:
把队尾的不满足H(q[r - 2], q[r - 1])(r - 1为队尾指针)>= H(q[r - 1], i)的元素q[r - 1]去掉,然后i入队,因为随着i的增加,2a·s[i]在不断减小,若又有H(q[r - 2], q[r - 1]) < H(q[r - 1], i),则必然H(q[r - 1], i)先失效,失效后q[r - 1]
就永远也不可能成为最优点,所以可直接把它从队列中Pass掉。
当然还需注意的是,最开始双端队列中必须有一个0。
Accode:
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
typedef long long int64;
int64 s[1000010], F[1000010];
int q[1000010];
int n, f, r;
int64 a, b, c;
inline int getint()
{
int res = 0; char tmp; bool sgn = 1;
do tmp = getchar();
while (!isdigit(tmp) && tmp != '-');
if (tmp == '-')
{
sgn = 0;
tmp = getchar();
}
do res = (res << 1) + (res << 3) + tmp - '0';
while (isdigit(tmp = getchar()));
return sgn ? res : -res;
}
#define sqr(x) ((x) * (x))
#define check(j, k, i) \
((F[j] - F[k] + a * (sqr(s[j]) - sqr(s[k])) \
>= (a * s[i] * (s[j] - s[k]) << 1)))
#define cmp(j, k, i) \
((F[j] - F[k] + a * (sqr(s[j]) - sqr(s[k]))) \
* (s[k] - s[i]) \
>= (F[k] - F[i] + a * (sqr(s[k]) - sqr(s[i]))) \
* (s[j] - s[k]))
//Pay attention to the brackets.
//用宏来优化函数。
int main()
{
freopen("commando.in", "r", stdin);
freopen("commando.out", "w", stdout);
n = getint(); a = getint();
b = getint(); c = getint();
for (int i = 1; i < n + 1; ++i)
(s[i] = getint()) += s[i - 1];
F[0] = 0; f = 0, r = 1; //
for (int i = 1; i < n + 1; ++i)
{
while (f < r - 1 && !check(q[f], q[f + 1], i))
++f;
F[i] = F[q[f]] + a * sqr(s[i] - s[q[f]]) + c;
while (f < r - 1 && !cmp(q[r - 2], q[r - 1], i))
--r;
q[r++] = i;
}
printf("%I64d\n", F[n] + b * s[n]);
return 0;
}
#undef sqr
#undef check
#undef cmp