决策单调性优化 DP
决策单调性优化 DP
这里面学问很深啊。优化方式也多种多样。
总的来说,对于一类形如 \(f_i=\min f_j+w(j,i)\) 的转移方程,设 \(p_i\) 为使 \(f_j+w(j,i)\) 取到最小值的点,那么如果 \(p_i\) 单调递增,我们就可以使用决策单调性优化 DP。
定理
这里给出一个定理:\(p_i\) 单调递增,当且仅当 \(w\) 满足四边形不等式,即
这个式子有更一般(但完全等价)的形式,即
更一般地,如果转移方程形如 \(f_i=\max f_j+w(j,i)\),那么将 \((2)\) 式若将不等号反向后成立,那么该转移同样满足决策单调性。证明基本一样。
不难用归纳法从 \((1)\) 推出 \((2)\),在 \((2)\) 中令 \(a=i,b=i+1,c=j,d=j+1\) 即得 \((1)\)。
略证:四边形不等式即 \(w(i,j)-w(i,j+1)\ge w(i+1,j)-w(i+1,j+1)\)
从这个式子可以看出来,四边形不等式实际上体现了某种意义上的凸性质。
因此大多数和「次幂」(如平方,根号)有关的式子都可以猜一手决策单调性。毕竟幂函数总是凸的。
因此,考虑 \(f_i\) 的决策点 \(p_i\),由 \(p_i\) 的最优性,对于任意的 \(j<p_i\),均有
我们证明:当 \(i\) 变为 \(i+1\) 时仍有 \(f_j+w(j,i+1)\ge f_{p_i}+w(p_i,i+1)\)。下面简记 \(p\) 为 \(p_i\)。
由于 \(w\) 满足四边形不等式,因此
这里 \(a,b,c,d\) 分别为 \(a=j,b=p,c=i,d=i+1\)
\((3)+(4)\) 即得 \(f_j+w(j,i+1)\ge f_{p_i}+w(p_i,i+1)\)。这就完成了证明。
二分队列
现在我们知道 \(p_i\) 单调递增,第一个想法是在转移时从 \(p_{i-1}\) 到 \(i-1\) 枚举 \(j\) 尝试转移,但是这样复杂度也不对。
我们考虑直接维护 \(p_i\) 这个序列,一开始将 \(p_i\) 全部设置为 \(1\),也就是先让所有 \(f_i\) 都从 \(1\) 转移。
我们的算法可以保证在计算到 \(i\) 时,\(p_i\) 恰好就是 \(f_i\) 的最优决策点。
在计算到 \(f_i\) 时,首先由 \(p_i\) 直接计算出 \(f_i\) 的值,接下来我们将根据 \(f_i\) 的值来更新后面的决策点。
具体来说,由决策单调性我们可以知道,一旦某个地方满足 \(p_j=i\),那么后面的 \(p_{j+1\cdots n}\) 都至少是 \(i\)。
而当前我们只考虑到 \(i\),也就是决策集合只有 \(1,2,\cdots,i\),因此实际上只需要找到这个位置 \(j\) 满足
- \(f_i\) 恰好是 \(f_j\) 的最优决策点,但是 \(f_{j-1}\) 的最优决策点在 \(i\) 之前
然后把 \(p_{j\cdots n}\) 全都赋值为 \(i\) 即可。找到这样的 \(j\) 可以使用二分。根据决策单调性,这样的 \(j\) 应当满足 \(p\) 数组中目前存储的决策点中,\(j\) 之前的决策都比 \(i\) 好,\(j\) 之后的决策都比 \(i\) 差,二分出这个位置即可。
现在得到这个位置之后,我们需要对 \(j\) 后面的位置进行整体赋值。你说我会用线段树维护序列,当然可以,不过这样做复杂度似乎要 $2\log $,而用下面的方式可以做到 $1\log $。
其实说出来也没什么,把序列划分成若干极长连续段,满足每个连续段中 \(p\) 的值都相同。接下来二分完成后只需要修改 \(O(1)\) 个连续段,删除若干连续段,并插入一个连续段即可。这其实就是所谓「珂朵莉树」。
分治优化
这一种优化方式仅限 \(g\to f\) 的情形,也就是给定 \(g\),我们需要算出 \(f_i=\min_{j<i} g_j+w(j,i)\) 的情形。
我们定义 \(\text{solve}(l,r,pl,pr)\) 表示需要计算出 \(f[l\cdots r]\) 的最优决策点,并且已经确定了每个决策点都在 \([pl,pr]\) 之内。考虑找到区间中点 \(m=\lfloor(l+r)/2\rfloor\),然后暴力计算 \(f_m\) 的最优决策点 \(p_m\)。
由决策单调性我们知道 \([l,m]\) 中的决策点都在 \([pl,p_m]\) 中,\([m+1,r]\) 的决策点都在 \([p_m,pr]\) 中,因此可以递归进 \(\text{solve}(l,m-1,pl,p_m)\) 与 \((m+1,r,p_m,pr)\)。
复杂度如何呢?画出分治树,分治树共 \(O(\log n)\) 层,在每一层中,所有的 \([pl,pr]\) 并起来恰好是 \([1,n]\),因此总的枚举次数不超过 \(n\log n\)。如果计算 \(w\) 的时间复杂度是 \(O(f(n))\),那么总的时间复杂度是 \(O(n\log n\times f(n))\)。
你或许已经发现,如果要算 \(f\to f\) 的转移,这种方法需要在计算 \(p_m\) 时已知前面的所有 \(f\) 值。
而这是不可能的,因此这种方法只适用于 \(g\to f\) 的转移,方法一则同时适用于 \(f\to f\) 和 \(g\to f\)。
但方法一也有缺点,下面我们就会说明。
大多数情况下 \(O(f(n))=O(1)\),不过也存在少数 \(f\) 不好计算的情况。
若贡献难以在常数时间内计算
对于这类贡献,虽然 \(w(l,r)\) 难以 \(O(1)\) 或 \(O(\log n)\) 计算,但 \(w\) 如果满足
- 在已知 \(w(l,r)\) 的情况下,我们可以快速算出 \(w(l,r+1)\) 以及 \(w(l-1,r)\)
那么同样可以使用分治法在 \(O(n\log n\times \cdots )\) 的时间复杂度内完成转移,其中 \(\cdots\) 部分是通过 \(w(l,r)\) 算 \(w(l,r+1)\) 与 \(w(l-1,r)\) 的复杂度。
解决方法很暴力:我们在全局维护两个指针 \(L,R\),每次需要计算 \(w(l,r)\) 时,直接暴力将 \(L,R\) 移动至需要查询的区间。这看起来非常暴力,但是我们可以证明:
- 按照正常的 DFS 序遍历分治树,指针的移动次数不会超过 \(O(n\log n)\)。
我们把左右端点分开考虑。
对于右端点,由于它总是被设为 \(m=(l+r)/2\) 后不动,接下来递归进 \(\text{solve}(l,m-1,\cdot)\) 与 \(\text{solve}(m+1,r,\cdot)\),因此指针总是从区间 \([l,r]\) 的某个端点移动到中点处,次数不超过 \(O(r-l)\),总的移动次数自然不超过 \(O(n\log n)\)。
对于左端点,它在移动完后总是等于 \(\min(m-1,pr)\),类似右端点可以证明左端点的移动次数是 \(O(pr-pl)\) 的。因此,总的移动次数同样是 \(O(n\log n)\)。
这就是分治法的优点所在,可以发现二分法是无法处理这样的贡献的。
有些文章把这种方法称为「整体二分」,我认为也有一定道理
对于 \(f\to f\) 类,且贡献难以计算的转移方程,我暂时没有好的解决办法,有鸽鸽教教我吗/kel
当决策值单峰
这里单峰(单谷)指的是,设 \(f_i\) 的最优决策点为 \(p\),则:
- \(p\) 之前的决策值 \(f_j+w(j,i)\) 随 \(j\) 增大单调递减,\(p\) 之后的决策值单调增。
这样,我们就可以直接维护 \(f_i\) 的最优决策点 \(p_i\),并在计算 \(f_{i+1}\) 时维护指针 \(j\) 从 \(p_i\) 开始往后跳,一旦发现 \(f_j+w(j,i)<f_{j+1}+w(j+1,i)\) 就说明找到了峰值,可以直接用 \(f_j+w(j,i)\) 更新 \(f_i\)。
由单峰的性质可以得到这样做的正确性。算法的时间复杂度是优秀的 \(O(n)\)。
不难发现这同样适用于贡献难算但可以快速移动端点的情形。
二分栈
咕
例题
POI2011 Lightning Conductor
很板的题,分 \(j<i\) 与 \(j>i\) 讨论,看到 \(\sqrt{i-j}\) 很容易猜到凸性质,因此具有决策单调性。两种优化方式均可。
NOI2009 诗人小 G
同样是幂函数,容易猜到凸性质。不过这次只能用第一种优化方式了。
CF868F Yet Another Minimization Problem
分 \(k\) 层转移,贡献不好计算但可以快速移动区间端点,使用分治法,复杂度 \(O(nk\log n)\)。代码巨好写
Luogu5774 CmdOI2019 任务分配问题
区间逆序对看上去也很有次幂的味道,我们猜它满足四边形不等式。
略证:四边形不等式即 \(w(l,r+1)-w(l,r)\ge w(l+1,r+1)-w(l+1,r)\)
我们发现左边恰好是 \([l,r]\) 中 \(<a_{r+1}\) 的元素个数,右边恰好是 \([l+1,r]\) 中 \(<a_{r+1}\) 的元素个数
显然左边不会比右边小,这就完成了证明。类似上题,用树状数组维护区间转移即可做到 \(O(nk\log ^2n)\)。