「SDOI2012」任务安排 3
知识点:斜率优化,二分
四 步 走 战 略
简述
给定一列 个有序的物品,每个物品有两个属性 ,给定参数 。
要求将物品分为任意段,第 段 的代价为 ,要求最小化分段的代价之和。
,,,。
1S,512MB。
分析
发现分到第几段对答案有影响,设 表示将前 个任务分为 段的最小费用和,转移时枚举段数 和最后一段,则有:
预处理前缀和,暴力转移时间复杂度 ,空间复杂度 。空间和时间都菜爆了。
发现在上述算法中必须枚举分到第几段,考虑能否优化掉状态的这一维,并优化转移。
这里用到了一种叫做「费用提前计算」的思想。发现每次转移将 这段分出后,后续元素的代价里都会加上 ,考虑在状态转移中加上这部分的影响。具体地,将状态删去一维,方程改写为如下所示:
状态转移方程很容易理解。此时已经无法准确定义 的含义了,但 一定表示将所有物品划分为某几段的最小代价和,且这样转移一定可以保证 的正确性。
预处理前缀和后暴力转移即可,时间复杂度 ,空间复杂度 。
但上述 算法不足以通过本题,发现出现了乘积项,考虑斜率优化。记 ,,代入转移方程并略作变换:
这是一个显然的斜率优化的形式,设:
如果 与 均单调递增,套路地单调队列维护下凸包即可。总时空复杂度均为 。
但本题中可能出现 , 是不单调的,这影响了最优决策点的选择,无法使用单调队列选择最优决策点。但 单调,使用单调队列维护下凸包的做法是正确的。
仍考虑使用单调队列维护下凸包,每次查询最优决策点时在凸包上二分,找到第一个使得左侧斜率小于 ,右侧斜率不小于 的位置即为最优决策点,不从队首弹出元素。可以发现此时的“单调队列”实际上是一个单调栈。我们实际上是在用 Andrew 算法 维护凸包。
总时间复杂度变为 。
代码
复制复制 //知识点:斜率优化 /* By:Luckyblock */ #include <algorithm> #include <cctype> #include <cstdio> #include <cstring> #define LL long long #define LD long double const int kN = 3e5 + 10; const LL kInf = 9e18 + 2077; //============================================================= int n, h = 1, t, q[kN]; LL s, ans = kInf, f[kN], sumt[kN], sumg[kN]; //============================================================= 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 sumg[x_]; } LD Y(int x_) { return f[x_] - s * sumg[x_]; } LD K(int x_, int y_) { if (X(x_) == X(y_)) return (Y(y_) > Y(x_) ? 1e18 : -1e18); return (LD) ((Y(y_) - Y(x_)) / (X(y_) - X(x_))); } bool Check(LD know_, int mid_) { return know_ <= K(q[mid_], q[mid_ + 1]); } int Query(int now_) { LD know = sumt[now_]; int ret = t; for (int l = h, r = t - 1; l <= r; ) { int mid = (l + r) >> 1; if (Check(know, mid)) { ret = mid; //q[ret] 是最靠右的使得 check 为 true 的位置,即 q[ret] 左侧斜率小于 k,右侧斜率不小于 k。 r = mid - 1; } else { l = mid + 1; } } return q[ret]; } void Insert(int now_) { while (h < t && K(q[t - 1], q[t]) >= K(q[t - 1], now_)) -- t; q[++ t] = now_; } //============================================================= int main() { n = read(), s = read(); for (int i = 1; i <= n; ++ i) { sumt[i] = sumt[i - 1] + read(); sumg[i] = sumg[i - 1] + read(); } Insert(0); for (int i = 1; i <= n; ++ i) { int j = Query(i); f[i] = f[j] + sumt[i] * (sumg[i] - sumg[j]) + s * (sumg[n] - sumg[j]); Insert(i); } printf("%lld\n", f[n]); 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】