Slope Trick 学习笔记
介绍
Slope Trick 常用于维护连续的分段一次凸函数。
维护拐点
该方法常用于求解最小化若干绝对值之和的问题,因为这类问题价值函数的斜率的绝对值不大,可以维护斜率变化时的拐点 \(p_{-n}\le\dots\le p_{-1}\le p_0\le p_1\le\dots\le p_n\),其中 \(p_i\) 表示斜率从 \(i\) 变为 \(i+1\) 的位置,如果 \(i\) 一次变化到了 \(i+k\) 则 \(p_i=p_{i+1}=\dots=p_{i+k-1}\)。
即维护大致形如
的凸函数。
[BalticOI 2004] Sequence (Day1)
先令 \(a_i\gets a_i-i,b_i\gets b_i-i\) 把严格递增改为非降。
设 \(f_i(x)\) 表示考虑前 \(i\) 个数,\(b_i\le x\) 的最小答案,那么有
把 \(f_i\) 看成一个分段一次函数,显然 \(f_i\) 是凸的,所求即 \(\min f_n(x)\)。对于转移,相当于先加上 \(|a_i-x|\) 再取前缀 \(\min\),加上 \(|a_i-x|\) 相当于将 \(a_i\) 之前的斜率 \(-1\),之后的斜率 \(+1\),所以在 \(p\) 中插入两个 \(a_i\)。然后前缀 \(\min\),相当于把斜率为正的删除,那么相当于把每次插入两个 \(a_i\) 然后将 \(p\) 中最大的删去,即删去现在的 \(p_0\)(一个 \(a_i\) 或原来的 \(p_0\)),可以用堆维护。最小值可以在 \([p_{-1}, p_0]\) 之间的 \(f\) 取到,那么取出堆顶时堆顶一定同时是原函数和新函数的最小值点,直接将答案加上 \(p_1-a_i\) 即可,构造方案的话,确定 \(d_n\) 使得 \(f_n\) 取到最小值之后倒序更新,只要构造让 \(f_{i-1}(d_i)\) 取到最小值的方案,即取 \(i-1\) 时的 \(p_0\) 和 \(p_0\) 和 \(d_{i}\) 的 \(\min\) 即可。时间复杂度 \(\mathcal{O}(n\log n)\)。
总结一下流程就是,转移的时候先加入拐点,然后用拐点计算最小值,再去掉斜率 \(>0\) 的部分。
这也是 L1 保序回归问题的一种解法。
[NOISG 2018 Finals] Safety
令 \(f_i(x)\) 为考虑到前 \(i\) 个数,\(b_i=x\) 的答案,有
相当于:先把 \(f_{i-1}(x)\) 变为其和 \(0_{[-h,h]}(x)\) 的 \((\min,+)\) 卷积,即所有负斜率段向左移动 \(h\),正斜率段向右移动 \(h\)。然后再插入两次 \(a_i\)。
用两个堆分别维护 \(p_{<0}\) 和 \(p_{\ge0}\),分别往两个堆插入 \(a_i\) 然后检查堆顶是否可以交换即可,道理同上面那题。懒标记维护平移,时间复杂度 \(\mathcal{O}(n\log n)\)。
维护斜率
其实就是维护差分。
CF865D
写出转移之后,由于 \(f(x)\) 是凸的,差分数组只会插入两个位置并弹出一个,堆维护即可,时间复杂度 \(\mathcal{O}(n\log n)\)。

浙公网安备 33010602011771号