疯狂学习——DP!
斜率优化
P3648 [APIO2014] 序列分割
可以观察到切的顺序无关,则有 \(f_{i,k}=\max f_{j,k-1}+(s_i-s_j)\times s_j\)。
则 \(f_{j,k-1}-s_j^2=-s_i\times s_j+f_{i,k}\) 。
决策单调性:分治
P4360 [CEOI2004] 锯木厂选址
预处理 \(w,d\) 前缀和以及 \(v_i\) 表示将全部木头移到 \(i\) 的代价。这样可以快速计算 \(cost(i,j)\) 表示将 \(i\sim j\) 全部木头移到 \(j\) 的代价。
枚举第二个锯木厂的位置,设 \(f_i\) 表示将第二个厂设在 \(i\),\(1\sim i\) 的最小代价。转移有 \(f_j=\min\{v_j+cost(i+1,j)\}\),答案即 \(\min\{f_i+cost(i+1,n+1)\}\)。由于 \(f\) 的转移显然具有决策单调性,分治解决。
CF833B The Bakery
显然具有决策单调性,使用「贡献难算」的技巧做到 \(\mathcal{O}(nk\log n)\)。
P5574 [CmdOI2019] 任务分配问题
「贡献难算」+树状数组做到 \(\mathcal{O}(nk\log ^2 n)\)。
CF1603D Artistic Partition
第一反应就是 DP,但是注意到 \(k\) 很大,怎么办??要想办法降维,观察到 \(c(l,r)\ge r-l+1\),所以当 \(n<2^k\) 时,直接令 \(x_i=2^{i-1}-1\),即可保证 \(c(l,r)=r-l+1\),此时取到理论最小值。
对于 \(n>2^k\) 的部分,DP。对 \(c(l,r)\) 化简
记 \(S(i)=\sum\limits_{j=1}^i\varphi(i)\),那么
可以证明其满足四边形不等式。整除分块可以做到 \(\mathcal{O}(n\log ^2 n\sqrt{n})\),已经足以通过。
但如果使用「贡献难算」的技巧,左端点移动时只需增加 \(S\left(\dfrac{r}{l}\right)\),时间复杂度 \(\mathcal{O}(1)\)。右端点移动时需要对于所有 \(\dfrac{r-1}{d}<\dfrac{r}{d}\) 的 \(d\) 增加 \(S\left(\dfrac{r}{d}\right)\),这只有在 \(d\mid r\) 时取到,所以时间复杂度 \(\mathcal{O}(n\log ^3n)\)。
决策单调性:二分队列
二分队列用来解决满足决策单调性,转移时的贡献能 \(\mathcal{O}(1)\) 计算的 DP 问题。通常情况下的限制为:贡献函数二阶导恒为非负,求最小值 或 二阶导恒为非正,求最大值。
具体的,建立一个储存三元组的队列 \((p,l,r)\) 表示 \(l\sim r\) 的最优决策点是 \(p\),步骤如下:
- 先判断队首三元组是否过时,若 \(r<i\) 则弹出队首,否则将 \(l\) 赋值为 \(i\)。
- 加入决策,如果 \(i\) 相比于队尾的 \(p\) 转移到 \(l\) 更优,根据决策单调性则 \((p,l,r)\) 就完全无用。重复这样操作。
- 取出队尾的三元组 \((p,l,r)\),我们要二分出一个位置 \(q\),使得 \(q\) 以前的位置从 \(p\) 转移更优,\(q\) 及其以后的位置从 \(i\) 转移更优。注意二分位置从 \(l\) 到 \(r+1\),若 \(q\leq n\) 那么先将队尾的 \(r\) 改为 \(q-1\),再将 \((i,p,n)\) 压入队列。
P1912 [NOI2009] 诗人小G
转移方程形如 \(f_i=\min f_j+|s_i-s_j+i-j-1-L|^P\),贡献可以快速计算。
P3515 [POI2011] Lightning Conductor
就是对于每个 \(i\) 找到最大的 \(a_j+\sqrt{|i-j}\),因为 \(j\in [1,n]\),所以要正反做一遍二分队列。注意用浮点数储存。
决策单调性:二分栈
二分栈用来解决满足如下的决策单调性的 DP 问题:每个决策点 \(j\) 只会被比它更前的决策点 \(i\,(i<j)\) 反超。通常情况下的限制为:贡献函数二阶导恒为非负,求最大值 或 二阶导恒为非正,求最小值。(与二分队列区分。)
用栈维护决策点,设现在栈顶的两个决策点分别是 \(i,j\,(i<j)\),现在要加入 \(k\)。我们二分出 \(i\) 反超 \(j\) 的时间 \(t_1\) 和 \(j\) 反超 \(k\) 的时间 \(t_2\),如果 \(t_1\leq t_2\),那么 \(j\) 就完全无用了,弹出 \(j\)。重复上述操作,最后压入 \(k\)。
当然每次转移前要不断二分出次栈顶反超栈顶的时间,如果小于 \(i\) 就不断弹栈。
P5504 [JSOI2011] 柠檬
设 \(f_i\) 表示以 \(i\) 结尾的答案。注意到一个关键结论:转移仅会发生在同种大小的贝壳之间。于是对每种贝壳单独 DP,转移函数 \((2st^2)''=2s>0\),且求最大值,于是使用二分队列。