[lnsyoj538/luoguP3628/APIO2010]特别行动队
题意
原题链接
给定序列 和自定义二次函数 ,要求将 分为几段(不妨设为 段),使得 的值最大,求最大的值
sol
设计状态转移方程。显然, 可以由 转移当且仅当 ,这表示从第 到第 个数都被分为一段。容易得到(设
本题复杂度较高,无法使用 的朴素方法 AC。
对于此类题目,我们可以采用斜率优化 DP 的方式优化。
由于在计算时, 是确定的,因此 及 均可视为常数。唯二的变量为 和 ,这启示我们将其变形成函数的形式,最终可以得到:
参考一次函数的 ,我们可以得到
由于我们最终要求的是最大的 ,即求出 最大,因此我们要求的就是使截距最大的 值。
同时,我们也可以据此求出有序实数对 ,从而在平面直角坐标系中表示出这些点
参考单调队列思想,我们可以找出永远不会作为最大值的所有点,此时,剩余的所有点一定会在这些点的上凸包(最小值为下凸包)上
因此,我们只需要维护这个凸包就可以了,计算的时候,结果即为第一个斜率小于 的线段的左侧点
对于此题,由于 的单调性,我们可以维护一个双向队列,每次当队头线段的斜率 时,就弹出队头;加入该点后不满足凸包性质,就弹出队尾;
代码
#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;
}
蒟蒻犯的若至错误
- 推式子把 的系数推没了(
分类:
题解 / 2024训练
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现