【动态规划】特别行动队

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

你可能感兴趣的:(【动态规划】特别行动队)