【算法介绍】斜率优化优化动态规划
算法简介:猫爪冻冻
这里我们讲的是斜率优化DP。需要用到较多的数学知识,算是 DP 优化中比较难的东西了。这里会尽量的去把它讲详细起来。斜率优化 DP 从字面上来讲就是通过斜率优化这个DP算法。接下来就从一个斜率优化的入门例题讲解下来。
算法演示:猎人射术
例题选讲:猫尾隐藏菜单
有 $N$ 个任务排成一个序列在一台机器上等待执行,它们的顺序不得改变。机器会把这 $N$ 个任务分成若干批,每一批包含连续的若干个任务。从时刻 $0$ 开始,任务被分批加工,执行第 $i$ 个任务所需的时间是 $T_i$ 。另外,在每批任务开始前,机器需要 $S$ 的启动时间,故执行一批任务所需的时间是启动时间 $S$ 加上每个任务所需时间之和。
一个任务执行后,将在机器中稍作等待,直至该批任务全部执行完毕。也就是说,同一批任务将在同一时刻完成。每个任务的费用是它的完成时刻乘以一个费用系数 $C_i$ 。
请为机器规划一个分组方案,使得总费用最小。
部分分 | 数值 | ||||
---|---|---|---|---|---|
第一档 | $n\le5\times10^3,1\le t_i,c_i\le100$ | ||||
第二档 | $n\le10^4,1\le t_i,c_i\le100$ | ||||
第三档 | $n\le3\times10^5,0\le | t_i | , | c_i | \le2^8$ |
例题解析:猫尾打烊之时
首先,我们一步一步来剖析这道题目。看向他的第一个数据点 $n \le 5\times10^3$ ,有点难评。。
不妨,先来点小菜开开胃,直接大力 $O(n^3)$ 试试。看见这种分成了一段一段的题目,我们不难直接想到了 $dp[i][j]$ 表示前 $i$ 个任务,分成了 $j$ 段这样的最小值作为状态。
那么,不难写出转移方程:
这个式子就是代表了你找到了一个点 $k$ 前面的开了另一段,这样的花费。代码也非常的简单,就不放了。
接下来,我们就要考虑如何把他优化到 $O(n^2)$ 去 AC 这第一个部分分。首先为了方便阅读,我们首先把上边的转移方程式子用前缀和的形式表达出来。分别定义了数组 $sumt[i]$ 表示 $\sum\limits_{p=1}^i t_p$ ,$sumc$ 也是同理,即 $\sum\limits_{p=1}^i c_p$。那么原来的转移方程式子,就可以改写成:
观察式子,发现 $j$ 的作用仅是为了计算此前个过程中的启动时间和,但事实上,既然这个时间要乘上此后的所有 $f$ ,不如提前加入其中。因为若分完前 $j$ 个任务后,要等待 $s$ 秒,则后续费用一定会加上 $(sumc[n]−sumc[j])×s$ ,于是可以提前加进去。换句话说,用图片表示出来两次的差别就是:
那么这时候的状态其实就已经不需要原来的 $j$ 的分段存在了,也就是说,状态少了一维,体现在复杂度上,就是复杂度从 $O(n^3)$ 退化到了 $O(n^2)$ 。
转移式子的话,就变成了(变量都还是原来的意思):
这样就是可以 AC 第一个部分分的算法了。其实也没什么技术难度,代码也就不放了。
接下来我们要进一步优化,再一次缩减一维也就是 $O(n)$ 。这要怎么办?我们这时候就要使用斜率优化了。顾名思义就是我们需要把这个按照斜率优化。
我们首先把这个式子一步一步的分解开来:
然后我们就把唯一的变量 $sumc[k]$ 的项提一下,也就是:
这时候,就已经很明显了,这个 $\min$ 里面的东西,一眼丁真就是一次函数啊。你看看这个 $s+sumt[i]$ 就是斜率吧,这个 $sumc[k]$ 作为变量是不是可以看作是 $x$ ,然后这个剩下的依托玩意就是 $b$ 了。
那么这样的一个东西我们拿到手上有什么用呢?
我们简单的考虑一下,这样的话,我们已经知道了这个一次函数的表达式,那么当前这个决策的坐标我们就知道了,也就是分别以 $sumc[k]$ 为 $x$ 轴,$dp[k]$ 为 $y$ 轴的坐标系上。而这个截距 $b$ 除了 $dp[k]$ 都是常量,我们可以直接算出来,并且第二档数据范围说过这个所有的 $t_i$ 和 $c_i$ 非负,也就是说,这个 $b$ 越大,当前的这个 $dp[k]$ 越大。
我们把上面这样可以更新 $dp[i]$ 的点 $k$ 称为决策点。
那么我们接下来要计算的东西就是,怎么样的节点算作最优呢?我们不妨设置两个点 $j, k$ 作为点 $i$ 的决策点,并且规定 $k\lt j \lt i$ 。
由于我们已经发现,截距 $b$ 越大,这个 $dp$ 值也越大,也就是说这样的单调性我嫩完全可以用单调队列维护。所以说,我们只需要维护一个单调队列,不断地记录斜率 $sumc[k]+s$ 的最小值即可。
那么接下来,就是重头戏了,第三档部分分,我们的 $t_i , c_i$ 非负性质被吃了,接下来该如何去做呢?
不难想到,我们要的决策点一定还是一样的,满足后面小于我,前面大于我,我们直接做二分即可,数据改用单调栈来维护单调上升。
代码也好写了。
1 | // #pragma GCC optimize(2) |
例题总结:附赠的下酒菜
这时候我们可以适当的讲一些玄学的知识了,我们不难发现,本道题目如果抽象一下,把斜率以一次函数的形式画出来,我们将会得到一个半凸包,具体点是下凸壳。
这时候,其实不难想到,斜率优化DP 就是一个在凸包上面维护一个值的过程。我们要求的决策点,有一些会破坏凸包的结构,也就是说,那些点都是不能要的。
而维护这样一个单调上升或下降的数据,我们可以用单调队列,单调栈以及我不会的李超线段树等等等等,这也是斜率优化DP的活性所在。
算法实战:最烈特调
查看相关标签喵!