[lnsyoj538/luoguP3628/APIO2010]特别行动队

题意

原题链接
给定序列 \(a\) 和自定义二次函数 \(f(x) = ax^2 + bx + c(a<0)\),要求将 \(a\) 分为几段(不妨设为 \(k\) 段),使得 \(\sum_{i=1}^{k} f(\sum_{j=l_i}^{r_i}a_j)\)的值最大,求最大的值

sol

设计状态转移方程。显然,\(dp_i\) 可以由 \(dp_j\) 转移当且仅当 \(j < i\),这表示从第 \(j + 1\) 到第 \(i\) 个数都被分为一段。容易得到(设 \(S_i = \sum_{j=1}^i a_j\)

\[dp_i = \max_{j=0}^{i-1}\{dp_j + a(S_i-S_j)^2 + b(S_i-S_j) + c \} \]

本题复杂度较高,无法使用 \(O(n^2)\) 的朴素方法 AC。
对于此类题目,我们可以采用斜率优化 DP 的方式优化。
由于在计算时,\(i\) 是确定的,因此 \(dp_i\)\(S_i\) 均可视为常数。唯二的变量为 \(dp_j\)\(S_j\),这启示我们将其变形成函数的形式,最终可以得到:

\[dp_j + aS_j^2 = (2as_i+b) \cdot S_j + (dp_i - as_i^2 - bs_i - c) \]

参考一次函数的 \(y=kx+b\),我们可以得到

\[y=dp_j + aS_j^2 \]

\[k=2as_i+b \]

\[x=S_j \]

\[b=dp_i-as_i^2-bs_i-c \]

由于我们最终要求的是最大的 \(dp_i\),即求出 \(b\) 最大,因此我们要求的就是使截距最大的 \(j\) 值。
同时,我们也可以据此求出有序实数对 \((S_j, dp_j + S_j^2)\),从而在平面直角坐标系中表示出这些点
参考单调队列思想,我们可以找出永远不会作为最大值的所有点,此时,剩余的所有点一定会在这些点的上凸包(最小值为下凸包)上
因此,我们只需要维护这个凸包就可以了,计算的时候,结果即为第一个斜率小于 \(k\) 的线段的左侧点

对于此题,由于 \(x,y\) 的单调性,我们可以维护一个双向队列,每次当队头线段的斜率 \(>k\) 时,就弹出队头;加入该点后不满足凸包性质,就弹出队尾;

代码

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;
typedef long long LL;

const int N = 1000005;

LL f[N], s[N], g[N], q[N];
int n, a, b, c;

LL x(int u){
    return s[u];
}

LL y(int u){
    return f[u] + a * s[u] * s[u];
}

int main(){
    scanf("%d%d%d%d", &n, &a, &b, &c);
    for (int i = 1; i <= n; i ++ ) scanf("%lld", &g[i]), s[i] = s[i - 1] + g[i];
    
    int hh = 0, tt = 0;

    for (int i = 1; i <= n; i ++ ) {
        while (hh < tt && y(q[hh + 1]) - y(q[hh]) >= (2 * a * s[i] + b) * (x(q[hh + 1]) - x(q[hh]))) hh ++ ;
        int j = q[hh];
        f[i] = f[j] + a * (s[i] - s[j]) * (s[i] - s[j]) + b * (s[i] - s[j]) + c;
        while (hh < tt && (y(q[tt]) - y(q[tt - 1])) * (x(i) - x(q[tt])) <= (y(i) - y(q[tt])) * (x(q[tt]) - x(q[tt - 1]))) tt -- ;
        q[ ++ tt] = i;
    }

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

    return 0;

}

蒟蒻犯的若至错误

  • 推式子把 \(aS_j^2\) 的系数推没了(
posted @ 2024-07-25 20:07  是一只小蒟蒻呀  阅读(9)  评论(0编辑  收藏  举报