AcWing 301. 任务安排2

​​题目传送门​​

​​转载链接​​

​​参考阅读链接​​

一、分析过程

1、与上一题的区别

在\(AcWing\) \(300\) 任务安排\(1\)中,我们使用了费用提前计算的技巧,实现了本点集拓扑题平方级别的算法,对于本题增加系统运维工资一般多少的数据范围,\(出队列操的报告词n斜率不存在是什么意思<=300000\),显然平方级别的算法不足以解决问题,需要想办法在线性的时间内解决本题。

2、引入凸包概念

本题需要使用​凸包(凸壳)优化​,先给一下​白话版本的凸包定义​:

给定二维平面上的一堆点中,​斜率不存在是什么意思凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点​。

如上图所示,点集中外层的点构成的​凸多边形​就构成了能够包含所有点的​凸包​,其中连出队列操的报告词接相邻顶点构成的边越来越平缓,或者说​斜率越来越小构成的一组点叫做上凸壳​,而相邻的边,​斜率越来越大的一组点叫做下凸壳​。本题主要考虑的是下凸壳。


                                            AcWing 301. 任务安排2

关于斜率,同学们可以认为是直线与横轴在第一象限的夹点集的概念角,夹角越小,斜率越小,夹角越大,斜率越大,换句话说:斜率越小,越贴近x轴,斜率越大,越贴近y轴。

如图所示,开始只有顶点\(A、B\)构成的凸包,然后加入第三点\(C_1\),显然\(BC_1\)的斜率是​高于​\(AB斜率为0\)的,因此\(AB\),\(BC_1\)构成了一个​下凸壳​;

但是如果新加的点不是\(C_1\)而是\(C_2\),\(BC_2\)的斜率小于\(AB\),那么\(AB\)和\(BC2\)就​不能构成下凸壳​了,因为不能作为点集的下边点集拓扑讲义第五版答案界,不系统运维工资一般多少能包含在\(AB\)下面却在\(AC_2\)上面的点,(这样就不是凸包,而是​凹包​了!)

因此,加入\(C_2\)后,\(AC_2\)将成为下斜率不存在凸壳新的边界了(用句人话说斜率k的公式就是\(C_2\)比\(B\)更靠外,用了\(C_2\系统运维工作内容)才能把所有点“​​”着包进来~)。

对于平面上的三点\(A(x1,y1),B(x2,y2),C(x3,y3)\),并且\(x1 < x2 < x3,y1 < y2 < y3\)。

\(AB\)的斜率小于\(BC\)的斜率才能使得\(AB\)、\(BC\)形成下凸壳。

3、凸包优化的思路

对于状态转移方程

\(f[i] = min(f[j] + (sc[i] - sc[j])*st[i] + (sc[n] - sc[j])*S)\)

联系题意,\(j\)表示的其实是第\(i\)个批次任务的前一个批次任斜率k的公式务的最后一个节点,在上一题中,通过遍历每一个可能的节点,然后找到合系统/运维理的节点\(j\)。

由于在求\(f[i]\)时,\(sc[i]\),\(sc[j]\),\(S\)都是已知量,对式子做下简单变形,得到:

\(f[j] = (st出队列操的报告词[i] + S) * sc[j] + f[i] - s斜率怎么求c[i] * st[i] -斜率 s系统运维工资一般多少c[n] * S\)

把变化的\(f[j]\),\(sc[j]\)放到了左侧,把一些在确定\(i\)时刻时的常量放到了右侧。

我们需要做的斜率公式是,找到合适的\斜率不存在是什么意思(j\),使得\(f[i]\)最小,将\(f[j]\)看作windows无法连接到打印机\(y\),\(sc[j]\)看作\(x\),\(st[i] + S\)看作\(k\),\(f[i] - sc[i] * st[i] - sc[n] * S\)看作\(b\),式子就化作了\(y = kx + b\)这样简单的一次函数,\(f[i]\)最小时,截距也就最小,所以问题就转化为了在所有\(斜率怎么求(sc[j],f[j])\)构成的点集中,找到一条斜率为\(st[i] + S\)的直线,使得​截距​最小。


                                            AcWing 301. 任务安排2
                                            AcWing 301. 任务安排2

如图所示,我们用一条斜率为\(k = st[i] + S\)的直线从下方不断往上移动,​最先​碰到​点集​中的点时,此时的​截距最小​,\(f[i]\)也就windows11有必要升级吗最小。

可以确定的是斜率怎么求最先点集的区间表示碰到的一定是凸多边形边界的顶点​,而不会是凸包内点集的概念部包含的点,所以,凸包以外的点可以排除了。

现在,需要确定的是,​使得截距最小的到底是凸包上的哪一点​?

图中直线上移最先遇见的显斜率k的公式然是\(B\)点,那在什么情况下最先遇见的不是\(AC\)而是\(B\)点呢,只需要

\(AB\)的斜率小于\(k\),\(BC\)的斜率大于\(k\)即可

4、单调队列补强凸包优化斜率越大直线越陡还是越缓

(1) 以队列维护下凸壳

也就说,我们需要出队列操的报告词维护一个​队列​(用来装凸包的​下凸壳​),自队头到队尾斜率递增,从头遍历队列斜率公式,最先遇见大于\(k\)的斜率的端点就是所求的\系统运维工程师(j\)点!

我们在遍历\(系统运维工程师i\)的时候逐步的去求\(f[i]\),求\(f[i]\)的时候用到了\(0\)到\(出队旗的正确姿势i-1\)(注:可以想一下原始版本的状态转移方程推导过程),也就说所有的\(j\),每次求出系统运维工作内容了\(i\)也需要将\(i\)作为\(i + 1\)状态中\(j系统运维工程师\)的候选者加入到点集中:上图中的点集分别是\(系统运维工资一般多少(sc[0],f[0]),(sc[1],f[1])\),...,每个人即是生产者,同时也是消费者。

由于\(sc\点集是什么意思)是前缀和,出队旗的正确姿势而且\(sc\)数组都是整数,所以随着\(i\出队旗的正确姿势)的增加,新加入的\((sc[j],f[j])点集和数集的区别\)一定是横纵坐标都超过之前的点的,我们需要不停的向点集windows无法连接到打印机中添加横纵坐标都增加的点,并且更新加入新点后点集的凸包。(越来越向右,是有方向滴~)

2、动态维护下凸壳队列

上面的下凸壳,是在进展到了\(i\)时的下凸壳点集,新加入的点,可能会替换掉原来队列中的点:

对于平面上的三点\(A(x1,y1)斜率公式,B(x2,y2),C(x3,y3)\),并且\(x1 &系统运维工资一般多少lt; x2 <斜率不存在是什么意思 x3,y1 < y2 < y3\)。\(AB\)与\(BC\)能够作为凸包当且仅当\(AB\)的斜率要小于\(BC\)的斜率。也就说,​原本凸包上有\(AB\)构成的边,加入\(点集的概念C\)后,要想\(BC\)也作为凸包的边界,只需要\(BC\)的斜率大于\(AB\),否则,就要删除\(B\)点,因为\(AC\)斜率为0肯定在\(B\)点的下方,\(斜率B\)不能作为凸包的边界了。

3windows11有必要升级吗、出队列头

我们遍历windows许可证即将过期怎么办\(i\)的时候,相当于不断的往点集中加点,并且更新凸包,在求\(f[i]\)时点集的概念,只需要找到斜率越大直线越陡还是越缓凸包​中第一个大于\斜率公式(斜率与倾斜角的关系k\)的斜率即可,由于凸包的边点集拓扑讲义第五版熊金城答案的斜率自左向右是递增斜率与倾斜角的关系的,所以我们可以维护一个队列,队头的斜率最小,并且自队头点集拓扑讲义向队尾斜率是递增的。我们还需要注意求\(f[i]\)时候的\(k = st出队序列[i] + S\),随着\(i\)的增加\(k\)也在增加,所以队列中小于\(k\)的斜率肯定也小于后面的\(k\),不可能成为最windows无法连接到打印机优解的,所以在遍历队列找到第一个大于\(k\)的斜率时可以将小于\(k\)的斜率都删掉。

4、出队列尾

为了维护队列的单调性,插入新点\((sc[i],f[i])\)时,如果该点与之前相邻点的斜率不是高于之前队尾的斜率的,就要将队尾的斜率出队,踢出凸包的点集,直至找到一个合适的位置再插入队列。

将上面单调队列的思路实现为具体的代码:

单调队列中存储的必然是点的编号\(i\),其代表的斜率应该与其更点集的概念接近队尾的相邻的点构成的斜率,所以初始情况下队头应该有个​哨兵节点\(0\)点集,后面要考虑的就是何时出队头,何时出队尾了。

出队头一方面是为了自出队序列小到大找到第一个大于\(k\)的斜率,另一方面是为了删掉小于\(k\)的斜率,一条线至少斜率不存在是什么意思有两个点构成,所以只有队列中有windows更新有必要吗不少于两个点​的情况下才能出队头,队头存储的是\((sc[q[hh]],f[q[hh]])\),与后一个点\((sc[q[hh]+1],f[q[hh]+1])\)构成的斜率是\((f[q[hh]+1] - f[q[hh]]) / (sc[q[hh]+1] - sc[q[hh]])\),当该斜率小于\(st[i] + S\)时就要出队头考虑下一个斜率,为了避免除法实现时需要变形下改为乘法。

出完队头就找到了最优解\(j\)点集拓扑学心得体会,开始更新\(f[i]\),然后就是将\((sc[i],f[i])\)加入队列,同样是需要比较斜率大小,将该点放在合适的位置使得队列斜率递增,当\((f[i] - f[q[tt]]) / (sc[i] - sc[q[tt]]) > (f[q[tt]] - f[q[tt]-1]) /windows10激活密钥 (sc[q[tt]] - sc[q[tt]-1])\)时才能加入到队尾。

另外,由于计算过程中的乘法可能会爆\(int\),所以本题的数组定义出队序列\(long\) \(long\)类型。

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 300010;
LL st[N]; //表示前i个任务完成所需的时间前缀和sum(Ti)
LL sc[N]; //表示前i个任务完成所需的费用前缀和sum(Ci)
LL f[N]; //总费用最小值
int q[N]; //单调队列

int main() {
int n, s;
cin >> n >> s;

for (int i = 1; i <= n; i++) cin >> st[i] >> sc[i], st[i] += st[i - 1], sc[i] += sc[i - 1];
int hh = 0, tt = 0;
q[0] = 0;//加入哨兵
for (int i = 1; i <= n; i++) {
//出队头
while (hh < tt && f[q[hh + 1]] - f[q[hh]] <= (st[i] + s) * (sc[q[hh + 1]] - sc[q[hh]])) hh++;

//利用公式更新最优解
f[i] = f[q[hh]] - (st[i] + s) * sc[q[hh]] + sc[i] * st[i] + s * sc[n];

//出队尾
while (hh < tt &&
(f[q[tt]] - f[q[tt - 1]]) * (sc[i] - sc[q[tt]]) >= (f[i] - f[q[tt]]) * (sc[q[tt]] - sc[q[tt - 1]]))
tt--;
//加入
q[++tt] = i;
}
//输出
printf("%lld\n", f[n]);
return 0;
}