CF311B Cats Transport
斜率优化,、 均单调,单调队列。
neko nyanyanya~
简述
一条数轴上有 个点,第 个点与第 个点的距离为 ,有 个人在第 个点上。有 只 neko,第 只 neko 位于第 个点上,neko 会在 时间开始等待人的到来。
没个人可以从任意时间从第 个点出发,按编号顺序依次经过各点,速度为 1 个单位长度 1 秒,中间不停下。每经过一个点,就会将该点上处于等待状态的 neko 接上。
每只 neko 的等待时间为其被接上时间与开始等待时间的差。要求安排每个人出发的时间,最小化 neko 等待的时间之和。
,,,,。
2S,256MB。
分析
可以简单地 YY 出一个与 相关的 DP 状态,但 过大,猜测 DP 状态与 无关,考虑找下结论。可以发现每个人接上的 neko 中,至少有一只恰好刚开始等待人的到来。正确性显然,若所有 neko 都是等待了一段时间后再被接上,不如提前人的出发时间,直到有一只刚开始等待,在不影响接上了哪些 neko 的前提下减少等待时间之和。
前缀和预处理点 到各点的距离 。对于每只 neko,容易求得一个人从何时出发能够恰好接上它,显然该时间为 。
考虑将所有 neko 按照 排序。显然在满足一开始的结论的前提下,每个人接到的 neko 都对应排序后的一段区间 ,刚开始等的 neko 一定是第 只 neko(即 最大的),则这个人的出发时间为 ,区间内 neko 的等待时间之和为 。
问题可以抽象为给定一长度为 的数列 ,要求将 分为 段,每段的代价为该段最右的数减去该段每个值的和,最小化代价和。
对于抽象后的问题,设 ,设 表示将前 个数分为 段的最小代价和,转移时先枚举段数 ,再枚举最后一段,则有:
方程中出现了乘积项,先通过枚举固定 后,这显然是一个可以斜率优化的形式,套路地设:
显然应最小化 ,又 与 都随 的增加单调不降,单调队列维护下凸包即可。
总复杂度 级别。
代码
复制复制//知识点:斜率优化 /* By:Luckyblock */ #include <algorithm> #include <cctype> #include <cstdio> #include <cstring> #define LL long long #define LD long double const int kN = 1e5 + 10; //============================================================= int n, m, p, head = 1, tail, q[kN]; LL sumd[kN], t[kN], s[kN], f[kN][101]; //============================================================= 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 x_; } LD Y(int id_, int x_) { return f[x_][id_] + 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 = t[now_]; while (head < tail && K(id_, q[head], q[head + 1]) <= know) ++ head; return q[head]; } void Insert(int id_, int now_) { while (head < tail && K(id_, q[tail - 1], q[tail]) >= K(id_, q[tail - 1], now_)) -- tail; q[++ tail] = now_; } void Init() { n = read(), m = read(), p = read(); for (int i = 2; i <= n; ++ i) sumd[i] = sumd[i - 1] + read(); for (int i = 1; i <= m; ++ i) { int h = read(), nowt = read(); t[i] = nowt - sumd[h]; } std::sort(t + 1, t + m + 1); for (int i = 1; i <= m; ++ i) s[i] = s[i - 1] + t[i]; } //============================================================= int main() { Init(); memset(f, 63, sizeof(f)); f[0][0] = 0; for (int k = 1; k <= p; ++ k) { head = 1, tail = 0; Insert(k - 1, 0); for (int i = 1; i <= m; ++ i) { if (i >= k) { int j = Query(k - 1, i); f[i][k] = f[j][k - 1] + 1ll * (i - j) * t[i] - s[i] + s[j]; } Insert(k - 1, i); } } printf("%lld\n", f[m][p]); return 0; }
【推荐】国内首个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】