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

题意

原题链接
给定序列 a 和自定义二次函数 f(x)=ax2+bx+c(a<0),要求将 a 分为几段(不妨设为 k 段),使得 i=1kf(j=liriaj)的值最大,求最大的值

sol

设计状态转移方程。显然,dpi 可以由 dpj 转移当且仅当 j<i,这表示从第 j+1 到第 i 个数都被分为一段。容易得到(设 Si=j=1iaj

dpi=maxj=0i1{dpj+a(SiSj)2+b(SiSj)+c}

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

dpj+aSj2=(2asi+b)Sj+(dpiasi2bsic)

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

y=dpj+aSj2

k=2asi+b

x=Sj

b=dpiasi2bsic

由于我们最终要求的是最大的 dpi,即求出 b 最大,因此我们要求的就是使截距最大的 j 值。
同时,我们也可以据此求出有序实数对 (Sj,dpj+Sj2),从而在平面直角坐标系中表示出这些点
参考单调队列思想,我们可以找出永远不会作为最大值的所有点,此时,剩余的所有点一定会在这些点的上凸包(最小值为下凸包)上
因此,我们只需要维护这个凸包就可以了,计算的时候,结果即为第一个斜率小于 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;

}

蒟蒻犯的若至错误

  • 推式子把 aSj2 的系数推没了(
posted @   是一只小蒟蒻呀  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示