AcWing 301. 任务安排2
. 任务安排
一、题目描述
有 个任务排成一个序列在一台机器上等待执行,它们的顺序不得改变。
机器会把这 个任务分成若干批,每一批包含连续的若干个任务。
从时刻 开始,任务被分批加工,执行第 个任务所需的时间是 。
另外,在每批任务开始前,机器需要 的启动时间,故执行一批任务所需的时间是启动时间 加上每个任务所需时间之和。
一个任务执行后,将在机器中稍作等待,直至该批任务全部执行完毕。
也就是说,同一批任务将在同一时刻完成。
每个任务的费用是它的完成时刻乘以一个费用系数 。
请为机器规划一个分组方案,使得总费用最小。
输入格式
第一行包含整数 。
第二行包含整数 。
接下来 行每行有一对整数,分别为 和 ,表示第 个任务单独完成所需的时间 及其费用系数 。
输出格式
输出一个整数,表示最小总费用。
数据范围
输入样例:
5
1
1 3
3 2
4 3
2 3
1 4
输出样例:
153
二、凸包优化【斜率优化】
这类问题做的过程比较偏数学
对于状态转移方程需要经过一些数学上的整理,之后几道题步步深入 斜率优化 问题
在 任务安排中,使用了 费用提前计算 的技巧,实现了 平方级算法 ,本题数据范围:,平方级算法不足以解决问题,需要 更牛 的算法。
引入前导知识 凸包:
1、凸包概念

如图所示,点集 中 外层的点 组成的 凸多边形 就构成了能够包含所有点的 凸包
- ① 相邻的边,斜率越来越小 的一组点叫做 上凸壳
- ② 相邻的边,斜率越来越大 的一组点叫做 下凸壳
2、凸包维护

如图:
① 开始只有顶点构成的 凸包,然后加入第三点,显然的斜率是高于的,因此构成了一个下凸壳;
尾部出队列的判定
② 如果新加的点不是而是,的斜率小于,那么和就不能构成下凸壳了,因为不能作为点集的下边界,不能包含在下面却在上面的点,因此,加入后,将成为下凸壳新的边界了。
为了维护好下凸壳,新加入的点,在未加入队列前,先检查队列头中元素与要新加入点之间的斜率,和队列中第一个、第二个点之间的斜率,如果 ,则从尾部弹出点,所有符合这样条件的点弹出后,再把新点入队列。
对于平面上的三点,并且。与能够作为下凸壳,当且仅当的斜率要小于的斜率。
3、利用凸包优化
朴素版的 状态转移方程:
当讨论到时,都是固定值、常数,变化的是含描述字样的数据项,提出式子中 含有单独 的常量:
考察 函数内部的 多项式:
我们知道 含 的项是一个 常量,故该 多项式 就能够 抽象 成如下形式:
注意:这里的 变量 和 变量 并不是两个 独立变量 ,变量 是与 有关的 变量,变量 也是与 有关的 变量
因此,不妨令 ,则该函数可以化为:
为了式子 方便观察,接下来我会把 写成 , 写成 ,但是请读者心中明白,这两个 变量,都是关于 的 变量
求 函数的极值问题,可以直观想到 直线方程:
对 直线方程 进行变形:
要求 函数的极值,就是求在 所有可能 的 点集 中,找到一个点 与当前 构成的 所有直线 中,轴截距最小,转化为 斜率固定的直线与某一个点集 之间的关系
一共有对数,即个点:
也就是在求上述 点集 与 当前斜率固定 为的直线 相交 时,最小的截距 是多少。
看图说话:

华罗庚:数缺形时少直观,形少数时难入微,数形结合百般好
图中,黑色点 为所有 的点 ,红色线 为 斜率 是 的 直线
我们 从下往上(截距从小到大) 去逼近当前 点集中的点
则 第一个 出现在直线上的点,就是满足 的 最小截距 ,即是当前阶段 的 最佳策略
那么,如何有效的维护点集呢?
这就是一个 线性规划 的问题了:在 点集 中,找到一个 点,绘制 固定斜率 的直线,使得 截距最小
由 线性规划 知识可知:我们只用考虑 点集 中 凸壳 上的点即可
解读:直线由下向上逼近,肯定先碰上下凸壳,肯定不会先碰上再往上的点集,告诉我们,其实只要维护好下凸壳就行,凸壳以上的点集既然用不到,就没必要计算,都没必要计算了,就没必要维护。
几何直观 上,显然这题要维护的是 下凸壳
因此,对于任意的 来说,我们只需去寻找 下凸壳 上的 点 构成 直线 的 最小截距 即可
这样 时间复杂度 在 最坏 的情况下,还是 (即 所有点 的 单增,,全部点集 构成一个 下凸壳)
斜率优化了个寂寞?并非如此!我们再看看直线方程中,各参数的性质
由于 都是 正整数,故它们的 前缀和 是 单调递增的
对应于 直线方程的参数: 是 单调递增 的, 也是 单调递增 的
而 下凸壳 中 相邻两点 构成的直线 斜率 也是 单调递增 的
则 下凸壳 中第一个出现在 直线 上的点,满足:,此时 直线截距 最小 (大家可以配合下图几何直观理解该处的不等式)

头部出队列的判定:
而又由于 单调递增,所以 之前 的 点 都不会是 点集 中出现在 直线 上的 第一个点
此时 只需维护点集区间 的 点 即可,直到 时,维护点集区间 变为
根据上述所说,出现了我们最熟悉的模型-滑动窗口模型
故我们可以直接利用 单调队列 来维护 下凸壳中的 有效点集
并用队头的 前两个点: 维护 大于当前斜率 的最小斜率
这里我把公式展开,方便大家理解:
把点插入 单调队列 前,先要保证 队列 中 至少有两个点,然后把 满足 的 点 弹出
即 新加入的点,必须和 原点集 构成 下凸壳, 无效点删除
这里我把公式展开,方便大家理解:
这样,队列 中 相邻两点 之间构成的直线 斜率单增,也就是我们的 有效下凸壳点集
4、【技巧】防止丢失精度
联立
避免除法丢失精度:
三、实现代码
时间复杂度:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 300010;
LL t[N], c[N], f[N];
int q[N];
int main() {
int n, s;
cin >> n >> s;
for (int i = 1; i <= n; i++) cin >> t[i] >> c[i], t[i] += t[i - 1], c[i] += c[i - 1];
int hh = 0, tt = 0; // 哨兵
for (int i = 1; i <= n; i++) {
/*
Q:为什么是 hh < tt ?
A:队列中最少有两个元素,才能考虑计算斜率,才能开始出队头
队列中一个元素,hh=0,tt=0
队列中两个元素,hh=0,tt=1 这样的情况下,保证了最少有一条边,才有斜率概念
(1) k=st[i]+S 所有数据是正数,所以k是单调递增的
(2) k从下向上移动,找到与点集的第一个交点,必然在下凸壳上
(3) 在相交时,其实是与三个点组成的两条直线进行相交,设 a,b,c,能够相交的必要因素是K_{a,b}<k<K_{b,c}
(4) 双向队列其实是一个单调上升的队列
(5) 双向队列中只保留斜率比当前ki大的下凸壳点集,比ki斜率小的点,以后也不会再使用,在i入队列前,删除掉
*/
// 队列头中的q[hh]其实是就是优解j,使得截距f[i]的值最小
// 要保证随时从队列头中取得的数,是下凸壳中第一个从k=st[i]+s大的,原来有比k小于等于的点都可以剔除了
while (hh < tt && f[q[hh + 1]] - f[q[hh]] <= (t[i] + s) * (c[q[hh + 1]] - c[q[hh]])) hh++;
// Q:为什么在未入队列前更新?
// A: 因为i是在查询它的前序j,找到了使它最小的j就对了,现在队列头中保存的就是使i最小的前序j,当然是放入队列前进行更新
f[i] = f[q[hh]] - (t[i] + s) * c[q[hh]] + c[i] * t[i] + s * c[n]; // 利用公式更新最优解
// 出队尾:现在凸包里记载的两个点之间斜率大于新加入值的清理掉
while (hh < tt && (f[q[tt]] - f[q[tt - 1]]) * (c[i] - c[q[tt]]) >= (f[i] - f[q[tt]]) * (c[q[tt]] - c[q[tt - 1]])) tt--;
q[++tt] = i;
}
// 输出
cout << f[n] << endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2020-01-19 如何使用印象的markdown
2017-01-19 最近的学习
2016-01-19 mydumper备份