有负权最短路的近线性算法 (Bringmann, Cassis, Fischer, 2023)
我们知道, 2022 年, Bernstein, Nanongkai 和 Wulff-Nilsen 提出了一个惊人的 \(O(m\log^8 m\log W)\) 的对于有负权图的最短路算法 [BNW22]. 今年, Bringmann, Cassis 和 Fischer 极大地优化了他们的做法, 得到了一个 \(O((m+n\log\log n)\log^2 n\log nW)\) 的做法 [BCF23], 约等于去掉了 \(6\) 个 \(\log\) 因子!
这里的 \(O(m + n\log \log n)\) 主要来源于 [MT04] 给出的整数边权最短路的一个算法, 和其他部分没有很深入的沟通, 所以本文介绍时只给出一个弱一点的结果, 也即 \(O((m+n\log n)\log^2 n\log nW)\), 只需要知道我们有类似 Fibonacci 堆之类的堆使得最短路可以在 \(O(m + n\log n)\) 时间内完成最短路的计算.
按照我的书写逻辑, 有些东西的定义不是一开始写出来就是精确的, 而是在讲述的过程中逐渐推出, 所以不能和论文的写作顺序完全对应.
前置
在我们对 Goldberg, 1995 的介绍里提到的 pricing (在 [BCF23] 中被称作 Johnson's trick) 和 scaling 这两个技术仍然是我们所依赖的基本技术. 大概来说就是:
- pricing (Johnson's trick) 是给一个没有负环的图进行重新赋权, 让它变成一个没有负权边的图.
- scaling 是说对于整数边权的图, 我们本质上只需要解决的是 \(w\geq -1\) 的情况.
如果不知道以上两点是什么意思, 可能需要看一看前文再继续阅读.
低直径分解的应用
[BNW22] 和 [BCF23] 的技术核心都在于考虑了所谓的低直径分解 (low diameter decomposition), 注意在这两篇文章里定义有所不同, 因为不同的要求会需要不同的算法, 也导出不同的后果, 这是 [BCF23] 能去掉很多个 \(\log\) 的关键.
对于一个图 \(G\), 假设没有负环, 我们知道这意味着可以计算出一个 \(G\) 上的势函数 \(\phi\). 这等价于我们外加一个节点 \(s\), 向每个节点连一条边权为 \(0\) 的边之后求最短路. 我们接下来假设求的最短路都是这个情况.
我们称 \(\kappa(G)\) 是 \(G\) 上的最短路中出现的最多的负边数量.
低直径分解是这样一个东西: 假设我们能找到一个边集的子集 \(S\), 使得:
- 删去 \(S\) 后, 图中每个强连通分量 \(C_i\) 要么满足 \(|C_i| \leq \frac 34 n\), 要么满足 \(\kappa(C_i) \leq \frac 12\kappa(G)\). 这被称为 progress.
- 对于 \(s\) 到任何一个 \(v\) 的最短路, 最短路经过 \(S\) 上的边数是 \(O(\log n)\) 的. 这被称为 sparse hitting.
现在, 假设我们真的有一个计算低直径分解的算法, 现在考虑如何运用它来计算出 \(\phi\).
算法 SSSP(\(G\), \(\kappa\)) 定义如下:
- 如果 \(\kappa \leq 20\log n\), 我们可以按照如下方法计算出最短路: 先删去所有的负权边跑一遍最短路, 然后用负权边松弛一遍, 再跑最短路. 如此执行 \(\kappa\) 遍之后, 就计算出了真正的最短路, 返回.
- 否则, 求出一个给出低直径分解的边集 \(S\), 对每个强连通分量 \(C_i\), 如果 \(|C_i| \leq \frac 34 n\), 调用 SSSP(\(C_i\), \(\kappa\)), 否则调用 SSSP(\(C_i\), \(\frac 12 \kappa\)). 从而得到这每个子图的势 \(\phi_i\).
- 考虑缩点之后的拓扑序, 我们可以 DP 得到整个图 \(G \smallsetminus S\) 的势函数 \(\phi\). 这使得重赋权之后 \(S\) 之外的边都是非负的.
- 依然使用 1. 中的: "跑一次最短路, 然后用 \(S\) 中的边松弛" 的策略, 直到松弛对答案没有任何改进为止, 此时给出了整个图 \(G\) 的势函数 \(\phi\).
由于递归的过程中 \(n \cdot \kappa\) 每次至少减少 \(\frac 34\), 可知递归的层数是 \(O(\log n)\) 的. 由于我们对低直径分解的假设, 可知 4. 中的松弛次数只有 \(O(\log n)\) 次.
因此, 忽略求解低直径分解的时间, 我们这部分的时间复杂度是 \(O((m+n\log n)\log^2 n)\) 的.
为了让低直径分解 真的存在, 我们其实还对 \(G\) 有若干假设, 比如 \(w\geq -1\), 所以外面还需要套一层 scaling, 这个 scaling 还有一些附加条件, 所以乘以的不是 \(\log W\), 之后我们详细讨论.
寻找低直径分解的算法
在叙述真正的低直径分解之前, 我们需要引入一些概念.
刚刚提到的 \(\kappa\) 其实有一定出入. 真正的 \(\kappa(G)\) 的定义是, 任意总长 \(\leq 0\) 的简单路径上出现的负边数量的最大值.
我们记 \(G_{\geq 0}\) 是把 \(G\) 中的负权边全都替换成 \(0\) 边.
对于非负权图 \(G\), \(B_G^{out}(v,r)\) 是所有 \(v\) 走 \(\leq r\) 的距离能到的点 (也就是 \(r\)-ball), \(\partial B_G^{out}(v, r)\) 是从 \(B_G^{out}(v,r)\) 到它之外的所有边 (也就是 boundary). 类似地, \(B_G^{in}(v,r)\) 和 \(\partial B_G^{in}(v,r)\) 是把 \(G\) 的所有边反向得到的对应的 ball 和 boundary.
我们称一个点是 out-heavy 的当其满足 \(|B_{G_{\geq 0}}^{out}(v, \kappa/4)| > n/2\), 称其是 out-light 的当其满足 \(|B_{G_{\geq 0}}^{out}(v, \kappa/4)| \leq 3n/4\). 显然我们可以类似地定义 in-heavy 和 in-light. 注意我们这里的分界点是 \(n/2\) 和 \(3n/4\), 也就是说介于这两个值之间的话就既是 heavy 的又是 light 的. 设计这一重合的目的在于, 我们有如下高效算法:
引理 (轻重判定 (Heavy-Light Classification)). 存在 \(O((m+n\log n)\log n)\) 复杂度的算法, 对每个节点 \(v\), 给出一个 label, 要么是 in-heavy 要么是 in-light, 并且有高概率使得对于每个节点, 这个 label 都是正确的.
这个引理的证明我们先放在后面, 我们先叙述真正的低直径分解算法.
算法 Decompose(\(G\), \(\kappa\)) 定义如下:
- 进行轻重判定, 得到被标记为 in-light 和 out-light 的点集, 记为 \(L^{in}\) 和 \(L^{out}\).
- 设边集 \(S\) 初始为空.
- while \(L^{out}\) 非空, 选取其中一个节点 \(v\), 以及生成一个随机数 \(r\), 服从 \(p = (20\log n)/\kappa\) 的几何分布. 给 \(S\) 加入所有 \(\partial B_{G\geq 0}^{out}(v, r)\), 并把 \(G\) 删去所有 \(B_{G\geq 0}^{out}(v, r)\) 中的点.
- 类似地, 对 \(L^{in}\) 做一样的事.
- 返回 \(S\).
让我们首先分析一下这个算法的复杂度. 首先 1. 是我们延后的东西, 我们预设它在 \(O((m+n\log n)\log n)\) 时间内完成了. 而对于 3. 4. 这两个步骤, 我们其实跑一个到了指定长度就停止的 Dijkstra, 然后掐掉就行了, OI 选手应该很容易脑补这是 \(O(m+n\log n)\) 的. 所以总时间就是 \(O((m+n\log n)\log n)\).
正确性: sparse hitting
接下来我们证明这个算法确实满足我们前面要求的 sparse hitting 性质.
对于任何一条边 \(e=(x,y)\in S\), 我们估计它落在 \(S\) 中的概率. 对于 \(v\in L^{out}\),
- 如果 \(x,y\notin B\), 说明它们都没被删掉, 被鸽到下一轮.
- 如果 \(x\in B\) 而 \(y\notin B\), 说明 \(e\in\partial B\), 被加到 \(S\) 里.
- 如果 \(y\in B\), 那么这轮就被删了, 绝不会再有机会加到 \(S\) 里.
我们可以考虑这么一个转化来控制总共发生的概率, 考虑将问题放宽: 我们有个 adversary 按照 \(L^{out}\) 的顺序一个个考虑每个节点. 在每个节点它可以选择掉用或者不用, 如果出现 \(x\in B\) 而 \(y\notin B\), 他就直接赢了. 如果 \(x,y\notin B\), 就还可以鸽到下一轮, 否则他直接输了. 显然这个游戏是 adversary 更强大的, 因为实际过程中 adversary 并不总能自由地选择.
因此, 设 \(q_i\) 是考虑 \(L^{out}\) 的第 \(i\) 个节点之后有机会被加到 \(S\) 的概率, 有
所以, 在 \(L^{out}\) 这个阶段, 被加到 \(S\) 里的概率是
那么对于任何超级源点 \(s\) 到一个点 \(u\) 的最短路 \(P\) 由于 \(\leq 0\), 而且 \(G\) 到 \(G_{\geq 0}\) 这个过程只有 \(-1\) 边被加了 \(1\), 所以有
对于 \(L^{in}\) 来说差不多, 所以 \(\mathbb E[|S\cap P|] \leq 40\log n\).
fix: 对 SSSP 的改进
注意到我们这里对 \(|S\cap P|\) 的控制并不是一致的, 我们没有排除这样一种情况: 虽然对所有 \(v\) 把 \(|S\cap P|\) 平均得到的值是很小的, 但有些有些 \(v\) 的 \(|S\cap P|\) 很大, 这个时候我们迭代的论数可能不是 \(O(\log n)\) 的.
所以, 我们要对松弛操作做一点点精细的调整, 我们只有那些在上一轮被松弛了的点才考虑下一轮再用负边松弛别的点. 这样就能保证每个点的松弛次数是不超过 \(|S\cap P|\) 的.
正确性: progress
我们考虑每次对于某个 \(v\in L^{out}\) 或者 \(v\in L^{in}\), 挖掉 \(B(v, r)\) 的时候, 这导致了所有的 SCC 一定和 \(B(v, r)\) 是不交叉的. 对于包含在 \(B(v, r)\) 内部的 SCC, 我们容易证明它是大小不超过 \(3n/4\) 的, 这是直接通过 light 性质保证的.
显然, 此时的 \(B(v, \kappa/4)\) 是不比一开始做轻重判定时候的 \(B(v, \kappa/4)\) 来的大的, 我们只需要证明 \(r\leq \kappa/4\) 的概率很大就行了, 我们有
所以 union bound 保证了有很大的概率, 在整个算法的执行过程中, 一次 \(r > \kappa/4\) 的事件都没有发生.
但是还剩下一部分 SCC 是在剩下的节点里的. 由于所有被标记成 light vertex 的都会被删掉, 我们剩下的 SCC 里的节点一定全都是 in-heavy 并且也是 out-heavy 的.
为了保证 \(\kappa(C)\leq \kappa/2\), 我们现在需要对图加上最后一条限制.
图的均值最小的回路 \(\overline w(C)\) 应该 \(\geq 1\).
显然这个性质仍然是传递的, 所以只要一开始保证了就行. 但显然并非无负环的图都能满足这个性质, 所以我们为了满足它, 需要对传统的 scaling 手段做一些修改, 这部分处理我们放在后面.
现在反设 \(\kappa(C) > \kappa/2\), 我们由此构造出一个均值 \(<1\) 的回路. 由于 \(\kappa(C) > \kappa/2\), 可知存在 \(u, v\in C\) 且他们之间有一条路径 \(P\) 满足 \(w(P)\leq 0\), 且经过了 \(>\kappa/2\) 条负边. 由于他们都是 heavy 的, 我们有它们对应的 \(|B^{out}|, |B^{in}| > n/2\), 因此必然交于某个点 \(w\), 我们有一条 \(v\) 到 \(w\) 的路径和 \(w\) 到 \(u\) 的路径, 它们在 \(G_{\geq 0}\) 上的距离 \(\leq \kappa/4\), 所以 \(D\colon u\to v\to w \to u\) 这个回路满足 \(w(D)\leq 0 + \kappa/4 + \kappa/4 \leq \kappa / 2\). 但是 \(D\) 有 \(>\kappa/2\) 条负边, 所以 \(\overline w(D) < 1\). 与我们的限制矛盾.
因此, 在满足限制的情况下, 没有被删掉的 \(G\) 中的点构成的 SCC 一定均有 \(\kappa(C) \leq \kappa/2\).
轻重判定的技术细节
注意我们只需要清晰地分辨出 \(> 3n/4\) 和 \(\leq n/2\) 的大小的 ball 就行了, 这提醒我们进行随机采样.
我们随机地选取 \(\delta \log n\) 个点, 计算 \(G_{\geq 0}\) 以它们为起点的单源最短路, 然后把距离 \(\leq \kappa/4\) 的点加上贡献. 这样每个点就有了对自己的 ball size 的估计, 如果一个 ball size 实际上是 \(\leq n/2\) 但采样的结果 \(> 3/4\), Chernoff bound 告诉我们这种事情发生的概率是 \(\exp \{ - \Omega(\delta \log n) \} = n^{-\Omega(\delta)}\), 所以取一个大一些的 \(\delta\) 就可以用 union bound 住整个算法过程中出错的概率.
这部分跑了 \(O(\log n)\) 次最短路, 所以复杂度是 \(O((m+n\log n)\log n)\).
scaling 的技术细节
这里采用的 scaling 策略稍微曲折一点: 若一张图 \(G\) 没有负环, 且所有边都 \(>-3W\), 那么可以通过一次 SSSP 的调用算出一个势函数, 让所有边都 \(> -2W\).
具体的方法是, 对于一张图 \(G\), 我们考虑图 \(H\) 让每条边 \(w_H = \lceil w_G / W\rceil + 1\), 显然 \(w\geq -1\) 满足了, 而且对于每个回路, 我们有
所以平均长度也满足了.
最后我们证明这个跑出来的势能确实让边权变大了. 对于 \(e=(u, v)\), 有
现在还剩下最后一个问题, 就是我们最后只是成功让图的边权 \(\geq -1\) 了.
所以我们实际上需要对刚刚这个过程做一点修改: 在最开始, 把整个图每条边的权值乘以 \(n\), 然后在 scaling 做到保证每条边 \(\geq -1\) 之后, 直接对 \(G_{\geq 0}\) 跑最短路得到最短路树.
我们分析这个做法的正确性: 由于我们一开始把整个图的边权乘以了 \(n\), 那么真实的最短路树中每条最短路都是 \(n\) 的倍数, 对于一条假的最短路而言, 每条边加上 \(1\) 的边权只能让它变大 \(n-1\), 是不能让它反超的.
所以, 我们 scaling 的实际迭代次数是 \(O(\log (nW))\) 次.
延伸
如此下列, 我们知道了, 在图不存在负环的时候, 本算法能在期望 \(O((m+n\log n)\log^2 n \log nW)\) 的时间内跑出解.
[BCF23] 中还进一步解释了如何在同样时间复杂度内找出一个负环, 甚至找出一个均值最小的环. 不过本文可以就此打住了.
参考文献
[BNW22] Aaron Bernstein, Danupon Nanongkai, Christian Wulff-Nilsen, 2022. Negative-Weight Single-Source Shortest Paths in Near-Linear Time.
[BCF23] Karl Bringmann, Alejandro Cassis, Nick Fischer, 2023. Negative-Weight Single-Source Shortest Paths in Near-Linear Time: Now Faster!
[MT04] Mikkel Thorup, 2007. Integer priority queues with decrease key in constant time and the single source shortest paths problem.