Slope Trick小记
应该是非常愚蠢的东西,作用是把常数大而且难写的的 \(O(n\log n)\) 优化成常数小而且好写的 \(O(n\log n)\)。
例题 CF713C。
先考虑令 \(a_i-i\) 然后转化成不降序列。
很显然最终答案应该是原序列中出现的数,设 \(dp[n][x]\) 表示 \(b_n=x\) 的最小代价就做完了。
转移方程为 \(dp[n][x]=\min_{y=1}^{x}dp[n-1][y]+|x-y|\)。
考虑这个过程是在干什么。
好像是在合把两个分段凸函数加起来。
这两个分段函数每一段都是一次函数。
先让我们简化问题,把值域缩小到 \(O(n)\) 看看会发生什么。
根据差分,前缀和以及斜率的定义,我们能发现相当于是把每一段 \([x,x+1]\) 的区间的斜率 \(+1/-1\)。
于是我们可以动态维护斜率和这个函数在 \(0\) 处的取值,显然如果斜率大于了 \(1\) 那么这一段将会被置为 \(0\)。
因为这是一个凸函数吗,所以斜率一定是单调的。
使用线段树即可做到 \(O(n\log n)\)。
容易发现若将一次函数改为 \(k\) 次函数,那么只需要对其微分即可。
而 Slope Trick 是钻了一个空子:斜率最大只有 \(n\)。
他维护了每个分割点,表示分割点与分割点直接的斜率差了 \(1\)。
于是就可以快速计算某处的值是多少。
不过其实 Slope Trick 维护的是最右边那个分割点上面的值,不影响就是。
以及上述维护方法会受到函数定义域的影响,你需要维护每一处点值,而如果要取任意点值就会寄,或者需要使用平衡树维护。
试试看!CF280E
仍然考虑设 \(dp[n][x]\) 表示 \(y_n=x\)。
那么转移应该是:
容易发现后面那部分是二次函数,如果仍然使用上述思路的话需要证明 \(dp[n][y]\) 为分段函数。
考虑归纳。对于第一段一定是一次函数。
关于 \([x-b,x-a]\) 可以考虑前缀取一个 \(\min\) 然后再平移。
考虑对于每一段自身平移。能够发现最多只是增加了一段 \(y=x+c\) 的部分,剩下部分仍然是二次函数。
考虑其他段平移到这一段的,能够发现是类似的东西。
所以这个操作结束后一定是分段二次函数。再加上一个二次函数还是分段二次函数。
此时使用上述方法维护每一段的斜率即可。因为仍然是凸函数所以可以使用平衡树维护斜率。(有平移操作)
复杂度 \(O(n\log n)\),但是我觉得即使是 \(O(n^2)\) 的也没什么人愿意去写吧。。。。。。
总结:对于一个凸函数,将其求导后维护其每一段也是可以维护其极值点的,这样会比维护原函数简单一些(?),不失为一种维护方法。
如果要一句话总结那就是 \(f(x)=f(0)+\int_0^x\frac{{\rm d}f(x)}{{\rm d}x}\)。