【问题描述】 你有一支由 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