「SDOI2016」征途
斜率优化,、 均单调,单调队列。
王 道 征 途
「そうですね…やっぱり僕は、王道を征く、ソープ系ですか」
简述
给定一列有序的 个物品,第 个物品的价值为 。
给定参数 ,要求将一列物品分成 段,最小化每段长度之和的方差 ,输出 。
,。
1S,256MB。
分析
记划分出的 段的和分别为 ,将 展开一下:
发现最后的式子中 等于 ,是一个定值,仅需最小化第一项即可。
按传统先写个暴力,设 表示将前 个数划分为 段时 的最小值,转移时枚举段数 和上一段的结尾 ,则有:
预处理前缀和后暴力转移复杂度 ,考虑优化。
发现后一项是一个区间和的平方的形式,它同时与 有关。考虑对 做一个前缀和,设 ,将其代入原式:
考虑先通过枚举固定 ,对于每一个决策 ,都有:
这是一个显然的斜率优化的形式,套路地设:
对于上式,显然应最小化截距 的值,易证最优决策点一定位于下凸包上。又 、 均随 的增加而增加,单调队列维护下凸包即可。注意一些初始化的小问题,详见代码。
总复杂度 级别。
代码
复制复制//知识点:斜率优化 /* By:Luckyblock */ #include <algorithm> #include <cctype> #include <cstdio> #include <cstring> #define LL long long #define LD long double const int kN = 3000 + 10; //============================================================= int n, m, h = 1, t, q[kN]; LL s[kN], f[kN][kN]; //代码中 f[j][i] 表示将前 i 个数划分为 j 段时 sum b_i^2 的最小值,与上述分析中相反。 //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } void Chkmin(LL &fir, LL sec) { if (sec < fir) fir = sec; } LD X(int x_) { return s[x_]; } LD Y(int id_, int x_) { return f[id_][x_] + s[x_] * s[x_]; } LD K(int id_, int x_, int y_) { if (X(x_) == X(y_)) return (Y(id_, y_) > Y(id_, x_) ? 1e18 : -1e18); return (LD) ((Y(id_, y_) - Y(id_, x_)) / (X(y_) - X(x_))); } int Query(int id_, int now_) { LD know = 2 * s[now_]; while (h < t && K(id_, q[h], q[h + 1]) <= know) ++ h; return q[h]; } void Insert(int id_, int now_) { while (h < t && K(id_, q[t - 1], q[t]) >= K(id_, q[t - 1], now_)) -- t; q[++ t] = now_; } //============================================================= int main() { n = read(), m = read(); for (int i = 1; i <= n; ++ i) s[i] = s[i - 1] + read(); memset(f, 63, sizeof(f)); f[0][0] = 0; for (int k = 1; k <= m; ++ k) { h = 1, t = 0; Insert(k - 1, 0); for (int i = 1; i <= n; ++ i) { if (i >= k) { int j = Query(k - 1, i); f[k][i] = f[k - 1][j] + (s[i] - s[j]) * (s[i] - s[j]); } Insert(k - 1, i); } } printf("%lld\n", 1ll * m * f[m][n] - 1ll * s[n] * s[n]); return 0; }
作者@Luckyblock,转载请声明出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】