「APIO2010」特别行动队 - 斜率优化 DP

一支部队由 名预备役士兵组成,士兵从 编号,要将他们拆分成若干特别行动队,同一队中队员的编号应该连续。

士兵 的初始战斗力为 一支特别行动队的初始战斗力 为各士兵初始战斗力之和。一支特别行动队的战斗力会被修正为 ,其中 已知,

求出将所有士兵组成若干特别行动队的最大总战斗力。

链接

BZOJ 1911

题解

表示前 名士兵分成若干特别行动队的最大战斗力, 表示前缀和。

枚举 ,将第 个分在同一队里,状态转移方程为

时间复杂度为 ,超时,需要优化。

考虑两个决策点 ),若 优,则有

不等式右边单调递减,左边分母上的前缀和单调递增。

用单调队列存储所有决策点,维护一个上凸壳,从左到后两两之间的斜率递减,且均小于当前的 ,每次最优决策从最左边取得。

时间复杂度为

代码

#include <cstdio>

const int MAXN = 1000000;

int n;
long long a[MAXN], A, B, C;
long long s[MAXN + 1], f[MAXN + 1];

template <typename T> inline T sqr(const T &x) { return x * x; }

inline long long y(const int a) {
    return f[a] + A * sqr(s[a]) - B * s[a];
}

inline long long x(const int a) {
    return s[a];
}

inline long long g(const int i) {
    return 2 * A * s[i];
}

inline double slope(const int a, const int b) {
    return static_cast<double>(y(a) - y(b))
         / static_cast<double>(x(a) - x(b));
}

int main() {
    int n;
    scanf("%d", &n);
    scanf("%lld %lld %lld", &A, &B, &C);
    for (int i = 0; i < n; i++) scanf("%lld", &a[i]);
    for (int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i - 1];

    static long long q[MAXN + 1];

    long long *l = q, *r = q;
    *r = 0;

    for (int i = 1; i <= n; i++) {
        while (l < r && slope(*(l + 1), *l) > g(i)) l++;

        int j = *l;

        // int _j = -1;
        // for (int j = 0; j < i; j++) {
        //     if (_j == -1 || f[j] + A * sqr(s[i] - s[j]) + B * (s[i] - s[j]) + C > f[_j] + A * sqr(s[i] - s[_j]) + B * (s[i] - s[_j]) + C) _j = j;
        // }
        // j = _j;

        // printf("i = %d, j = %d\n", i, j);
        f[i] = f[j] + A * sqr(s[i] - s[j]) + B * (s[i] - s[j]) + C;

        // printf("i = %d, _j = %d\n", i, _j);

        while (l < r && slope(*r, *(r - 1)) < slope(i, *r)) r--;

        *++r = i;
    }

    printf("%lld\n", f[n]);

    return 0;
}