《算法分析与设计》学习笔记
作业占 \(20\%\),期中占 \(40\%\),项目(读 Paper,尝试实现或改进)占 \(40\%\)。\(\newcommand{\de}{\delta}\newcommand{\al}{\mathcal}\newcommand{\fr}{\frac}\newcommand{\tio}{\tilde {\al O}}\newcommand{\a}{\alpha}\)
Lecture 1:近似最短路
内容:Dijkstra,APASP,近似距离查询。
很多内容 xtq 在 WC2024 上讲过。
最短路问题
记 \(\de(s, t)\) 表示 \(s\) 到 \(t\) 的最短路,满足三角形不等式。
使用斐波那契堆优化的 Dijkstra 的复杂度为 \(\mathcal{O}(m + n \log n)\)。
对于 APSP,Floyd-Warshall 的复杂度为 \(\mathcal{O}(n ^ 3)\)。更优?无向无权图 \(\al{O}(n ^ {\omega})\approx \al{O}(n ^ {2.38})\),有向无权图 \(\mathcal{O}(n ^ {\mu})\approx \mathcal{O}(n ^ {2.53})\)。猜想:当边权是 \(1\sim n\) 的整数时,不存在 \(\mathcal{O}(n ^ {3 - \de})\) 的 APSP 算法。
无向无权图 APASP
设 \(d(u, v)\) 为近似距离。
- stretch \(k\) 近似满足 \(\de(u, v) \leq d(u, v)\leq k\de(u, v)\)。
- surplus \(t\) 近似满足 \(\de(u, v) \leq d(u, v)\leq \de(u, v) + t\)。
\(\tio(n ^ {2.5})\) surplus \(2\)
准备工作
独立随机以 \(\fr 1 k\) 的概率选中一个点,得到期望大小为 \(\al {O}(\fr n k)\) 的点集 \(D\)。对于度为 \(d\) 的点,其有 \(1 - (1 - \fr 1 k) ^ d\) 的概率和 \(D\) 相邻。取 \(d = ck\log n\),则概率为 \(1 - \fr 1 {n ^ c}\)。
定理
给定度数 \(d\),可以找到大小为 \(\tio(\fr n d)\) 的点集 \(D\),满足所有度不小于 \(d\) 的点大概率和 \(D\) 相邻。
设 \(V_1\) 是所有度不小于 \(\sqrt n\) 的点,\(D_1\) 是大小为 \(\tio (\sqrt n)\) 的点集,满足 \(V_1\) 的每个点和 \(D_1\) 相邻。
设 \(E_2\) 是所有 \((u, v)\) 满足 \(u\notin V_1\) 或 \(v\notin V_1\),则 \(\deg(u)\) 或 \(\deg(v)\) 不超过 \(\sqrt n\),\(|E_2| = \al O(n\sqrt n)\)。
算法流程
- 从每个 \(u\in D_1\) 跑 BFS 求出 \(\de(u, v)\),其中 \(u\in D_1\),\(v\in V\)。时间 \(\al O(|D_1||E|) = \tio (n ^ 2\sqrt n)\)。
- 从每个 \(u\in V\) 在 \(E_2\cup \de(D_1\times V)\) 上跑 Dijkstra。时间 \(\tio(n(|E_2| + n|D_1|)) = \tio(n ^ 2\sqrt n)\)。
算法的时间复杂度 \(\tio(n ^ 2\sqrt n)\)。
算法分析
- 若 \(u\in D_1\) 或 \(v\in D_1\),则 \(\de(u, v)\) 在第一步求出。
- 若 \(u, v\) 最短路径上所有点的度小于 \(\sqrt n\),则每条边在 \(E_2\) 上,\(\de(u, v)\) 在第二步求出。
- 若存在度不小于 \(\sqrt n\) 的点 \(w\),则 \(w\) 和 \(w'\in D_1\) 相邻,\(d(u, v)\leq \de(u, w') + \de(w', v) \leq \de(u, v) + 2\)。
通过 \(D_1\) 把路径上度数较大的点移动到较小的集合内。
\(\tio(n ^ {7 / 3})\) surplus \(2\)
这个算法还有可以利用的性质:最短路径要么只有 \(\de(D_1\times V)\),要么只有 \(E_2\),两部分独立。\(\delta(D_1\times V)\) 的部分已经是近似长度,但 \(E_2\) 的部分求出了精确值,这里还有优化空间。
改改阈值会发生什么?
设 \(V_1\) 是所有度不小于 \(n ^ {2 / 3}\) 的点,则 \(|D_1| = \tio(n ^ {1 / 3})\)。算法的第一步时间 \(\tio (n ^ {7 / 3})\),但 \(|E_2| = \al O(n ^ {5 / 3})\),无法 Dijkstra。这一步解决了 \(u\) 或 \(v\) 度不小于 \(n ^ {2 / 3}\) 或最短路径上有度不小于 \(n ^ {2 / 3}\) 的点的情况,且 接下来只需考虑边集为 \(E_2\) 的情况,设新图为 \(G'\)。
再分一层 \(V_2\) 表示所有度不小于 \(n ^ {1 / 3}\) 的点,则 \(|D_2| = \tio(n ^ {2 / 3})\)。设 \(V_2\) 对应 \(E_3\),则 \(|E_3| = \al{O}(n ^ {4 / 3})\)。这一步解决了最短路径上所有点度小于 \(n ^ {1 / 3}\) 的情况(实际上,没有两个相邻的点度均不小于 \(n ^ {1 / 3}\) 的情况)。
对于路径上有度在 \(n ^ {1 / 3}\) 和 \(n ^ {2 / 3}\) 之间的点的情况,设 \(w\) 是最后一个这样的点,则 \(w\) 到 \(v\) 都在 \(E_3\) 上,存在 \(w'\in D_2\) 和 \(w\) 相邻。我们要求 \(u\) 和 \(w'\) 之间在 \(G'\) 上的最短路 \(\de'(u, w')\)。这可以通过从 \(D_2\) 的每个点 BFS 得到,时间 \(\al{O}(|D_2||E_2|) = \tio(n ^ {7 / 3})\)。
但这样还有一个问题,就是 \(\de'(D_2\times V)\) 的大小是 \(\tio(n ^ {5 / 3})\),不能直接丢进 Dijkstra。设 \(E ^ *\) 为每个 \(w\in V_2\) 和一个邻居 \(w'\in D_2\) 之间的边集,则 \(|E ^ *| = \al O(n)\)。将 \(E ^ *\) 丢入最终的 Dijkstra,这样就只要求最终能考虑到 \(\de'(\{u\}\times D_2)\)。所以从 \(u\) 跑 Dijkstra 的时候只要把 \(\de'(\{u\}\times D_2)\) 丢进去就行。
算法流程
- 从每个 \(u\in D_1\) 跑 BFS 求出 \(\de(u, v)\),其中 \(u\in D_1\),\(v\in V\)。时间 \(\al O(|D_1||E|) = \tio (n ^ {7 / 3})\)。
- 从每个 \(u\in D_2\) 在 \(E_2\) 上跑 BFS 求出 \(\de'(u, v)\),其中 \(u\in D_2\),\(v\in V\)。时间 \(\al O(|D_2||E_2|) = \tio (n ^ {7 / 3})\)。
- 从每个 \(u\in V\) 在 \(E_3\cup \de(D_1\times V)\cup \de'(\{u\}\times D_2)\cup E ^ *\) 上跑 Dijkstra。时间 \(\tio(n(|E_3| + n|D_1| + |D_2| + n)) = \tio(n ^ {7 / 3})\)。
通过 \(D_1\) 把路径上度数较大的点移动到较小的集合内,并删去 \(E_2\) 以外的所有边。
通过 \(D_2\) 把路径上度数适中的点移动到较小的集合内,只需保证最后一个度数适中的点被考虑到,并以此减少 Dijkstra 算法的边数。
算法分析
- 若 \(u\in D_1\) 或 \(v\in D_1\),则 \(\de(u, v)\) 在第一步求出。
- 若 \(u, v\) 最短路径上所有点的度小于 \(n ^ {1 / 3}\),则每条边在 \(E_3\) 上,\(\de(u, v)\) 在第三步求出。
- 设 \(w\) 是路径上最后一个度在 \(n ^ {1 / 3}\) 和 \(n ^ {2 / 3}\) 之间的点,\(w'\in D_2\) 且 \((w, w')\in E ^ *\)。路径 \(\de'(u, w') + \de(w', w) + \de(w, v)\leq \de(u, v) + 2\) 且三部分分别属于 \(\de'(\{u\}\times D_2)\),\(E ^ *\) 和 \(E_3\)。
扩展
分更多层会出现更多中转点,可以做到 \(\tio(kn ^ {2 + \fr 1 {3k - 4}})\) 的无向无权图 surplus \(2(k - 1)\) APASP。当 \(k = \al O(\log n)\) 时,存在 \(\tio (n ^ 2)\) APASP。
无向无权图 surplus \(2\) APASP 在 2022 年被邓老师等人改进到了 \(\tio(n ^ {2.29})\)。
\(\tio(n ^ {2.29})\) surplus \(2\)
普通 \(\min +\) 矩阵乘法(距离乘法)暂无 \(\al O(n ^ {3 - \de})\) 的算法,但 BD 矩阵(只需一个方向的 BD)存在 \(\tio(n ^ {2.687})\) 的距离乘法。
大概思想是观察到 \(\de(D_1\times V)\) 的部分只有两条边,相当于 \(\de(V\times D_1)\) 和 \(\de(D_1\times V)\) 的距离乘法。用欧拉序将矩阵化为 BD,把这部分复杂度变优,就可以更改阈值做到更优复杂度了。
没讲怎么改进 \(\de(D_1\times V)\) 的求解。
分成 \(\al O(\log n)\) 层可以进一步做到 \(\al O(n ^ {2.2593})\)。
\(\tio(n ^ 2)\ (2, 1)\) 近似
\(\de(u, v)\leq d(u, v)\leq 2\de(u, v) + 1\)。
算法流程
对每个 \(0\leq i\leq \log_2 n\),设 \(V_i\) 为所有度不小于 \(\fr {n} {2 ^ i}\) 的点,得到大小为 \(\tio(2 ^ i)\) 的覆盖集 \(D_i\) 和对应的 \(E ^ *\)。设 \(E_i\) 为所有 \(u\notin V_{i - 1}\) 或 \(v\notin V_{i - 1}\) 的边。
- 从 \(D_i\) 的每个点在 \(G_i = (V, E_i\cup E ^ *)\) 上跑 BFS 求出 \(\de_i(D_i\times V)\)。时间 \(\al O(|D_i|(|E_i| + n)) = \tio(n ^ 2)\)。
设 \(c_i(u)\) 为 \(G_i\) 上距离 \(u\) 最近的 \(D_i\) 的点。
- 用 \(\de_i(u, c_i(u)) + \de_i(c_i(u), v)\) 和 \(\de_i(u, c_i(v)) + \de_i(c_i(v), v)\) 更新 \(d(u, v)\)。
算法分析
考虑 \(u, v\) 最短路上度数最大的点 \(x\)。因为 \(V_0 = \varnothing\),所以存在 \(i\) 使得 \(x\in V_i\) 但 \(x\notin V_{i - 1}\)。此外,因为 \(x\) 的度数最大,所以路径上所有边都在 \(G_i\) 上。设 \(w\in D_i\) 和 \(x\) 相邻且 \((w, x)\in E ^ *\),则 \(u\rightsquigarrow x\to w\) 和 \(w\to x\rightsquigarrow v\) 都在 \(G_i\) 上。
设 \(u' = c_i(u)\),\(v' = c_i(v)\),于是 \(\de_i(u, u') \leq \de(u, x) + 1\),\(\de(v, v')\leq \de(v, x) + 1\),不妨设 \(\de_i(u, u') \leq \lfloor\fr {\de(u, v)} {2}\rfloor + 1\),则
- 当路径长度是偶数时,如果 \(x\) 不在中点,不妨设靠近 \(u\) 一侧,那么 \(\de_i(u, u') + \de_i(u', v)\leq 2\de(u, v)\)。如果 \(x\) 在中点,那么考虑度数次大的点 \(x'\) 以及对应的 \(j\),根据 \(E_j\) 的定义可知路径上所有边在 \(E_j\) 上,此时 \(x'\) 一定不在中点。
- 当路径长度是奇数时,\(\de_i(u, u') + \de_i(u', v) \leq 2\de(u, v) + 1\)。当 \(x\) 和 \(x'\) 分别是最中间一条边的两端时,不等式可能取等。
为什么无向无权
对于有向图,以上算法显然行不通。
对于带权图,和一个点相邻不一定距离这个点近。可以考虑乘法近似。
无向带权图 APASP
截断 Dijkstra
考虑求出距离一个点最近的 \(b\) 个点,普通 Dijkstra 需要 \(\tio(n\cdot b)\) 的时间。截断 Dijkstra 在普通算法上加入改进:只考虑一个点权值最小的 \(b\) 条出边。
时间 \(\tio(b ^ 2)\)。
\(\tio(n ^ {7 / 3})\) stretch \(3\)
类似的思想。
设 \(B(u)\) 是距离 \(u\) 最近的 \(n ^ {2 / 3}\) 个点,\(D\) 是随机选出的大小为 \(\tio(n ^ {1 / 3})\) 的点集,满足所有 \(B(u)\) 和 \(D\) 有交。
这样,从 \(D\) 出发跑 Dijkstra,如果 \(u\in D\) 或 \(v\in D\) 或 \(v\in B(u)\),得到最短路。否则存在 \(w\in B(u)\cap D\)。因为 \(v\notin B(u)\),所以 \(\de(u, w)\leq \de(u, v)\)。同时 \(\de(w, v)\leq \de(w, u) + \de(u, v)\leq 2\de(u, v)\),所以 \(\de(u, w) + \de(w, v) \leq 3\de(u, v)\)。
显然复杂度为 \(\tio(n ^ {7 / 3})\)。
Cohen-Zwick 在 1997 年做到了 \(\tio(n ^ {3 / 2}m ^ {1 / 2})\) stretch \(2\),\(\tio(n ^ {7 / 3})\) stretch \(\fr 7 3\),\(\tio(n ^ 2)\) stretch \(3\)(可用 \(\tio(n ^ 2)\ (2, 1)\) 近似解决)。
近似最短路查询
以上讨论给出了使用 \(\al O(n ^ {5 / 3})\) 空间,\(\tio(n ^ {7 / 3})\) 预处理的 \(\al O(1)\) stretch \(3\) 近似最短路查询。
可以用 \(\al{O}(kn ^ {1 + \fr 1 k})\) 的空间做 \(\al O(k)\) stretch \(2k - 1\) 近似最短路查询 [Thorup, Zwick, 2001]。
Lecture 2:斐波那契堆
内容:二叉堆、二项树、斐波那契堆。
以下所有内容考虑小根堆:每个点的权值不大于其儿子权值。
二叉堆
堆的结构是一棵完全二叉树。根是最小值,树高 \(\log_2 n\)。
如何实现?用指针维护父亲和儿子关系,或左儿子两倍右儿子两倍加一。
查询最小值
\(\al O(1)\)。
插入
将 \(x\) 插入下一个叶子的位置,向上冒泡。\(\al O(\log n)\)。
减法
将元素的值减去 \(k\),向上冒泡。\(\al O(\log n)\)。
删除最小值
将根和最右侧叶子交换,向下冒泡(和较大的儿子交换)。\(\al O(\log n)\)。
合并
暴力合并。\(\Omega(n)\)。
二项堆
二项树
\(B_0\) 是一个点,\(B_k\) 是 \(B_{k - 1}\) 在根底下接一棵 \(B_{k - 1}\)。
性质:
- \(|B_k| = 2 ^ k\)。
- \(B_k\) 深度为 \(k\)。
- \(B_k\) 的第 \(i\) 层有 \(\binom k i\) 个点。
定义一个 结点的阶 是其儿子个数,一棵 二项树的阶 是其根的阶。
原则:只合并同阶二项树。
二项堆
二项堆维护的过程借助了二进制分组的思想保证复杂度:一旦有两棵同阶二项树,则合并。因此,若二项堆的大小为 \(n\),则该二项堆恰含所有 \(n\) 在 \(2\) 进制下为 \(1\) 的位对应的二项树。
一个二项堆最多有 \(\lceil \log_2 n\rceil\) 棵二项树。
合并
模拟二进制加法。\(\al O(\log n)\)。
删除最小值
找到最小值,删去。对应二项树分裂成若干二项树,与原有二项树合并。\(\al O (\log n)\)。
减法
向上冒泡。\(\al O(\log n)\)。
删除
减到 \(-\infty\) 再删除最小值。\(\al O(\log n)\)。
插入
二进制模拟加 \(1\)。\(\al O(\log n)\),均摊 \(\al O(1)\)。
查询最小值
\(\al O(\log n)\)。
斐波那契堆
斐波那契堆只有删除(最小值)的复杂度为 \(\al O (\log n)\)。其余操作均为均摊 \(\al O(1)\)。
从空斐波那契堆开始,任何 \(a_1\) 次插入、\(a_2\) 次删除和 \(a_3\) 次减法的操作序列需要 \(\al O(a_1 + a_2\log n + a_3)\) 时间。
做到 \(\al O(1)\) 插入、删除和减法是不可能的,因为基于比较的排序有下界 \(\Omega(n\log n)\)。因此,斐波那契堆相当优秀。
其基本想法和二项堆类似,但对结构的要求更宽松。二项堆在每次操作后立刻重构,斐波那契堆采用 懒重构 的思想,直到删除最小值之后再重构。
维护:
- 一些堆。
- 根的列表和最小值指针。
- 结点的标记:表示该结点已经失去了一个儿子。如果再失去一个,则以它为根的子树会被剪下来。
设 \(R(x)\) 和 \(R(H)\) 表示阶,\(T(H)\) 表示树的个数,\(M(H)\) 表示被标记的结点数量。定义势能 \(\Phi(H) = 2T(H) + M(H)\)。
定义均摊时间为实际用时加上势能的变化量,则总用时不超过总均摊时间之,因为势能非负且初始为 \(0\)。势能可能需要乘以一些常数。
插入
将结点插入根的列表,更新最小值指针。
时间 \(\al O(1)\),势能 \(+1\),均摊 \(\al O(1)\)。
合并两棵树
将较大的根连到较小的根下面。
删除最小值
把根的所有子树全部融到根列表,重构使得没有根的阶相同。
时间 \(\al O(R(H) + T(H))\),势能 \(\al O(R(H')) - T(H)\),均摊(给势能乘以一定常数)\(\al O(R(H))\)。
在只有前述操作时,\(R(H) = \al O(\log n)\)。
减法
如果堆性质没有被破坏,则直接减。
否则把以 \(x\) 为根的树剪下,加入根列表,标记其父亲(除非其父亲为根)。
如果不特殊处理,这一步会扭曲树的形态,导致 \(R(H)\) 过大。为了保证树的平整,如果一个点已经被标记两次,则取消标记,并将以该点为根的子树剪下,标记其父亲(除非其父亲为根)。这一步可能递归。
设剪下子树的次数为 \(c\),则时间 \(\al O(c)\),势能 \(\al O(1) - c\),均摊 \(\al O(1)\)。
分析
将 \(x\) 的儿子按照连边顺序记为 \(y_1, \cdots, y_k\)。当 \(y_i\) 连向 \(x\) 时,\(R(y_i)\) 等于当时的 \(R(x) \geq i - 1\)。而接下来 \(y_i\) 最多失去一个儿子。于是 \(R(y_i)\geq i - 2\)。
设 \(F_k\) 为 \(k\) 阶树的最小大小,则 \(F_1 = 1\),\(F_1 = 2\),\(F_k = F_{k - 2} + \cdots + F_0 + 1 = F_{k - 2} + F_{k - 1}\),这是斐波那契数列。于是 \(R(H)\leq \log_{\phi} n\)。
合并
用双向列表维护根列表。
时间 \(\al O (1)\),势能 \(0\),均摊 \(\al O(1)\)。
应用
\(\al O(m + n\log n)\) 最小生成树
在 Dijkstra 或 Prim 的过程中使用斐波那契堆,\(n\) 次插入,\(n\) 次删除和 \(m\) 次减法,时间 \(\al{O}(m + n\log n)\)。
\(\al O(m + n\log ^ *n)\) 最小生成树
Fredman & Tarjan [1987] 通过限制堆的大小做到了更优的复杂度:考虑结合 Boruvka 和 Prim,从某个点开始,如果堆里有 \(k\) 个元素或碰到了当前生成森林,则停止本次 Prim,从另一个未被访问的点开始。一轮过后,将森林里的所有树缩点,则每个点至少 \(k\) 条出边,结点数量不超过 \(\fr {2m} k\)。
取 \(k = 2 ^ {2m / n}\),则一轮复杂度 \(\al O(m + n\log k) = \al O(m)\)。新的 \(k' = 2 ^ {2m / n'} = 2 ^ {2 ^ {2m / n}}\)。可知总轮数为 \(\beta(m, n) = \min\{i: \log ^ {(i)} n \leq \fr m n\}\),其中 \(\log ^ {(i)} n\) 表示取 \(i\) 次 \(\log\)。
因此,当 \(m = \al O(n)\) 时,时间为 \(\al O(m \log ^ * n)\)。一旦存在常数 \(k\) 使得 \(m = n\log ^ {(k)} n\),则时间为 \(\al {O}(m)\)。
目前学术界的 MST
随机算法 \(\al O(m)\) [Karger, Klein, Tarjan 1995]。
确定算法 \(\al O(m\alpha(m, n))\) [Chazalle 2000]。
最优算法(复杂度仍为 open problem) [Pettie, Ramachandran 2002]。
判定 MST \(\al O(m)\) [Dixon, Rauch, Tarjan 1992]。
Tarjan 老爷子是真牛啊。
Lecture 3:并查集
内容:并查集的复杂度分析。
问题描述
初始 \(n\) 个集合 \(\{1\}, \cdots, \{n\}\),支持合并两个集合,以及查找某个元素在哪个集合。
并查集是维护不交集合的数据结构,每个集合用一个代表元表示,支持:
- Makeset(x):新建包含元素 \(x\) 的集合。
- Find(x):查询 \(x\) 所在集合的代表元。
- Union(x, y):若 \(x, y\) 处于不同集合,则合并它们所在的集合,删去原有的两个集合。
以下设 \(n\) 为元素数量,\(m\) 为操作次数。
链表
用链表维护,每个元素指向下一个元素和代表元。
makeset \(\al O(1)\),find \(\al O(1)\),union \(\al O(n)\)。
如果不维护代表元指针,则 find \(\al O(n)\),union \(\al O(1)\)。
启发式合并
将较短的链表添加在较长的链表后,时间 \(\al O(m + n\log n)\):元素代表元更新,其所在链表长度至少翻倍。
Up-tree
每个点指向其父节点,没有父节点的点为对应集合代表元。
合并可能导致树的深度达到 \(\al O(n)\),使得 find \(\al O(n)\)。
按秩合并
将深度较小的树合并到深度较大的树:深度为 \(k\) 的树至少有 \(2 ^ k\) 个点。
将大小较小的树合并到大小较大的树:每个点被合并时,其所在集合大小至少翻倍。
时间复杂度 \(\mathcal{O}(m\log n)\)。
路径压缩
查询时将路径上的每个点都指向代表元。
复杂度分析
重点是分析路径压缩按秩合并的并查集的复杂度。
设 \(r(x)\) 表示 \(x\) 的秩,初始为 \(0\)。\(r(x)\) 增加 \(1\) 当且仅当 \(y\) 合并到 \(x\) 上且 \(r(x) = r(y)\)。可知阶为 \(r\) 的点数为 \(\al O(\fr {n} {2 ^ r})\)。
因为 union 等价于两个 find 和一个 link,且 makeset 和 link 显然为 \(\al O(1)\),所以只考虑 \(m\) 个 find 的均摊时间。
\(\al O((m + n)\log \log n)\)
考虑 \(r(p(x)) - r(x)\)。每次查询,最多 \(\al O(\log \log n)\) 个点的该值没有翻倍,因为每个这样的点都会让 \(r(rep(x)) - r(x)\) 翻倍。
对于那些翻倍的点,最多发生 \(\al O(\log r(x)) = \al O(\log \log n)\) 次。
时间为 \(\al O((m + n)\log\log n)\)。
\(\al O(m + n\log ^ *n)\)
设 \(T(m, n, r)\) 表示 \(n\) 个点 \(m\) 次操作且秩不超过 \(r\) 的最大指针赋值次数,显然 \(T(m, n, r) \leq nr\)。
设 \(s\) 是秩的阈值。
考虑一次 find 从 \(x\) 开始,且根为 \(y\)。
- 若 \(r(y) \leq s\),则总赋值次数不超过 \(T(m, n, s)\)。
- 若 \(r(x) > s\),则总赋值次数不超过 \(\fr {nr} {2 ^ s}\)。
- 若 \(r(x) \leq s < r(y)\):
- 若 \(r(p(x))\leq s\),考虑到每个点的父亲的秩最多一次从不超过 \(s\) 变成大于 \(s\),总赋值次数不超过 \(n\)。
- 若 \(r(p(x)) > s\),每次操作最多一个这样的点,总赋值次数不超过 \(m_+\),其中 \(m_+\) 表示涉及到阶大于 \(s\) 的点的 find 次数,\(m_- = m - m_+\)。
于是
取 \(s = \log r\),则
\(\al O((m + n)\a(n))\)
Ackermann's function
定义
分析
对于
第二项不超过 \(T(m_+, \fr n {2 ^ s}, r) \leq m_+ + \fr {2n\log ^ * r} {2 ^ s}\),于是
取 \(s = \log ^ * r\),则
记 \(T''(m, n, r) = T(m, n, r) - 2m\),则
得
不断重复上述过程,得
\(\log ^ {* ^ c} r\) 表示取多少次 \(\log ^ {* ^ {c - 1}}\) 可以让 \(r\) 变成常数,而
因此时间为 \(\al O((m + n)\a(n))\) [Tarjan 1975]。
是否存在更优美的复杂度分析?Tarjan 同时证明了这个算法的时间下界 \(\Omega(m\a (n))\)。
复杂度下界分析
略。
对任何并查集,有 \(\Omega(\a(n))\) word-operations 的下界 [Fredman, Saks 1989]。
Lecture 4:动态树
Splay
设 \(\mu(x) = \log |S(x)|\),\(\Phi(S) = \sum_{x\in S} \mu(x)\)。
Splay 引理:每个 splay(x, S)
操作需要 \(3(\mu(S) - \mu(x)) + 1\) 的均摊时间。
- 每个 Zig 不超过 \(3(\mu(S) - \mu(x)) + 1\)。
- 每个 Zig-Zig 和 Zig-Zag 不超过 \(3(\mu'(x) - \mu(x))\)。
证明的 nasty-case 是 \(\mu(x) = \mu'(x)\),此时可以证明 \(\mu'(x) + \mu'(y) + \mu'(z) < \mu(x) + \mu(y) + \mu(z)\)。
动态最优性猜想:\(C_{\rm{SPLAY}}(s) = \al O(n + C_{\rm{OPT}}(s))\)。
LCT
对 link 的要求:\(v\) 和 \(w\) 不在同一棵树,且 \(v\) 是根。
对 delete 的要求:删除树边。
每个结点有 0/1 个偏好的孩子。用 Splay 维护偏好链,每个 Splay 指向父亲。
access 打通 \(x\) 到根的路径,模拟即可。
ETT
用 Splay 维护欧拉序。
换根:考虑 \(r\) 的任意一次出现,将欧拉序分为 \(A\) 和 \(B\),其中 \(A\) 是 \(r\) 之前的部分。删除 \(A\) 的第一个点,加入 \(r\),然后将 \(A\) 放在 \(B\) 之后。
换根之后容易做 link 和 cut。
LCT 做链问题比较优秀,ETT 做子树问题比较优秀。
Lecture 5:动态连通性
完全动态连通性
加边删边查询连通性。想法:维护生成森林。
加边容易,但删树边时需要找到重新连接两部分的边。
每条边有只会增加的等级 \(l_e\),设 \(E_i\) 为等级不小于 \(i\) 的边,则 \(E_0\supseteq E_1 \supseteq \cdots \supseteq E_{l_\max}\)。
维护 \(E_i\) 的生成森林 \(F_i\),满足
- 若 \((u, v)\in E_i\) 是非树边,则 \(u, v\) 在 \(F_i\) 上连通(生成森林极大)。
- 若 \((u, v)\in E_i\) 是树边,则 \((u, v)\) 在 \(F_j(j < i)\) 上也是树边。
- \(F_i\) 的每棵树的大小不超过 \(\fr {n} {2 ^ i}\)。
可知若一条边在 \(E_i\) 上是非树边,那么它在所有它在的 \(E_j\) 上是非树边。
加入 \((u, v)\) 时,将其加入 \(E_0\)。若 \(u, v\) 连通,则为非树边,否则为树边。
删除 \((u, v)\) 时,在所有 \(E_{0\sim l}\) 上删去 \((u, v)\)。若是树边,则删去 \((u, v)\) 会将 \(F_l\) 对应的树分裂成 \(T(u)\) 和 \(T(v)\) 两部分。其中一部分的大小小于原树的一半,不妨设为 \(T(u)\),则将 \(T(u)\) 加入 \(F_{l + 1}\)。然后检查所有和 \(T(u)\) 相连的非树边,若不连接 \(T(u)\) 和 \(T(v)\),则一定在 \(T(u)\) 内部,移到下一个等级,否则直接连起来。如果等级 \(l\) 没有,那么检查等级 \(l - 1\),以此类推。
动态子图连通性
忽略所有 \(\log\) 因子。
每个点有开关状态,支持修改一个点的状态,以及查询两个点能否通过开着的点连通。
维护两个集合 \(P, Q\),初始所有点都在 \(P\),\(Q\) 为空。\(P\) 只会删点。打开一个点时将其加入 \(Q\)。每 \(m ^ {2 / 3}\) 次修改重构一次,所以 \(Q\) 的大小不超过 \(m ^ {2 / 3}\)。
用删边维护的方法维护 \(P\) 的连通性。称总度数大于 \(m ^ {1 / 3}\) 的连通块为大连通块,数量不超过 \(m ^ {2 / 3}\),否则为小连通块。
维护新图 \(G ^ *\),结点包含 \(Q\) 和 \(H\),其中 \(H\) 的每个元素表示 \(P\) 的一个大连通块,以及原图连接 \(Q\) 以及 \(Q\) 和 \(H\) 之间的边。
如果 \(Q\) 的结点可以通过小连通块连通呢?若 \(u, v\in G\) 通过小连通块连通,则将 \((u, v)\) 加入 \(\Gamma\),其大小为 \(m ^ {4 / 3}\)。将 \(\Gamma\) 加入 \(G ^ *\),则 \(u, v\in Q\) 连通当且仅当它们在 \(G ^ *\) 上连通。
对于查询:
- 对于 \(Q\) 里的点,\(\log n\) 查询在哪个连通块。
- 对于大连通块的点,查询在 \(G ^ *\) 上表示这个大连通块的点,再查询在哪个连通块。
- 对于小连通块的点,找一个 \(Q\) 里的点与其相邻。若不存在则孤立。\(m ^ {1 / 3}\)。
对于预处理:
- \(G ^ *\) 和 \(\Gamma\) 都需要 \(m ^ {4 / 3}\) 的时间。
- 每 \(m ^ {2 / 3}\) 次修改重构一次,所以均摊到每个修改 \(m ^ {2 / 3}\)。
对于修改:
- 当加入或删除 \(Q\) 的点时,更新 \(G ^ *\) 的每个点和这个点的连接情况。\(m ^ {2 / 3}\)。
- 删除小连通块的点时,重新计算相关的 \(\Gamma\)。\((m ^ {1 / 3}) ^ 2 = m ^ {2 / 3}\)。
- 删除大连通块的点时,按度数之和从大到小记分裂成的连通块为 \(R_1, \cdots, R_k\)。
- 删掉 \(P\) 的边的总数为 \(m\),所以每个修改 \(m ^ {1 / 3}\)。
- 如果 \(R_i(i\geq 2)\) 是大连通块,则更新 \(G ^ *\) 需要 \(\deg(R_i)\) 的时间。\(\deg (R_i)\) 减半,最多 \(\log m\) 轮。\(m ^ {1 / 3}\)。
- 如果 \(R_i\) 从大连通块变成小连通块,则更新 \(\Gamma\)。因为只会变一次,所以 \(m ^ {2 / 3}\)。
Lecture 6:最大流
FF
每次找一个增广路。若所有容量是 \([0, U]\) 的整数,则算法在 \(|f ^ *|\leq nU\) 轮结束。当 \(U = 1\) 时,时间 \(nm\)。
整性定理:若网络所有容量都是整数,则存在所有边的流量都是整数的最大流。
EK
选增广路的时候不要随便选。选最大瓶颈路,足够大容量的路,或者最短路。
Capacity Scaling
设 \(\Delta\) 为不小于 \(U\) 的最大的二的幂。在边权 \(\geq \Delta\) 的图上找增广路,最多找 \(\al O(m\Delta)\) 次,然后将 \(\Delta\) 除以 \(2\)。\(m ^ 2 \log U\)。
需要强多项式算法。
最短路
指边数最少的路径。
考虑最短路分层图,每次删去至少一条边,反边不会出现在分层图上。最多增广 \(m\) 次后得到分层图上的阻塞流,最短路增加。\(m ^ 2 n\)。
Dinic
不要每次都在分层图上重新找,一次找完即可。\(n ^ 2 m\)。
用 LCT 维护分层图的生成树,\(nm\log n\)。
单位容量最大流
任意图
\(\al O(m)\) 的时间找到阻塞流,因为每条路径上的所有边被立刻删去。
\(n ^ {2 / 3}\) 轮之后存在相邻两层 \(n_i + n_{i + 1} \leq 2n ^ {1 / 3}\),剩余流量不超过 \(n ^ {2 / 3}\)。
\(m ^ {1 / 2}\) 轮之后存在相邻两层 \(m_i\leq m ^ {1 / 2}\),剩余流量不超过 \(m ^ {1 / 2}\)。
\(m \min(n ^ {2 / 3}, m ^ {1 / 2})\)。
无向图
在残量网络上,只有有流量的边有方向。在单位容量无向图上,任何无环流可以被分解为路径。
如果残量网络的最大流为 \(v\),则最短路不超过 \(\fr {n} {\sqrt v}\),因为每相邻两层至少有 \(2\sqrt v\) 个点。
因为最大流不超过 \(n\),所以无环流最多有 \(\fr n {\sqrt n} + \fr n {\sqrt {n - 1}} + \cdots + n = n ^ {3 / 2}\) 条边。因此寻找增广路时最多有 \(n ^ {3 / 2}\) 条有向边。对于无向边,用完全动态连通性维护,BFS 时只需考虑 \(n ^ {3 / 2}\) 条边。复杂度 \(\tio (n ^ {5 / 2})\)。
Lecture 7:最小割和 k 连通性
全局最小割
有平凡的 \(n\cdot \operatorname{flow}\) 做法,略。
Karger' Algorithm
随机选一条边收缩,只剩两个点时得到割,但不一定是最小割 \(c\)。当且仅当割的每条边都没有被收缩时正确。
因为每个点的度数不小于 \(c\),所以边数不小于 \(\fr {nc} 2\)。第 \(i\) 次选中的概率为 \(\fr {c} {(n - i)c / 2} = \fr 2 {n - i}\),所以正确率为
于是复杂度 \(n ^ 4\)。同时证明了全局最小割的数量 \(\al O(n ^ 2)\)。
考虑在还剩 \(\fr n {\sqrt 2}\) 个点时停下,此时正确率为 \(\fr 1 2\)。在剩下的小图上跑两次。时间
正确率
即 \(P(n) \geq P\left(\fr n {\sqrt 2}\right) - \fr 1 4P\left(\fr n {\sqrt 2}\right) ^ 2\)。设 \(q(k) = \fr 4 {P(k)} - 1\),则 \(q(n)\leq q\left(\fr n {\sqrt 2}\right) + 1 + \fr 1 {q\left(\fr n {\sqrt 2}\right)}\)。
设 \(Q(n)\) 是 \(q(n)\) 的上界,则 \(Q(n)\geq \log_{\sqrt 2} n + K\)。展开递归式,由调和级数得
不能用在 s-t 最小割,因为度数下界没有保证。
k 连通性
最简单的一集。
k 边连通:删去少于 \(k\) 条边,仍连通。
k 点连通:删去少于 \(k\) 个点,仍连通。要求 \(G\) 有多于 \(k\) 个点。
若 k 点连通,则 k 边连通。
双连通分量的判定:Tarjan。
耳分解不在考纲内,略。
Lecture 8:原始对偶松弛算法
集合覆盖
一个元素的频率为它所在的集合数量,\(f\) 为最大频率。点覆盖是 \(f = 2\) 的集合覆盖。
集合覆盖写成整数线性规划 IP:对每个 \(S_i\),有变量 \(x_i\in \{0, 1\}\) 表示是否选择。最小化 \(\sum_{i = 1} ^ k c(S_i)x_i\),要求 \(\sum_{i : e\in S_i} x_i \geq 1,\ \forall e\in U\) 且 \(x_i\in \{0, 1\}\)。
松弛成线性规划 LP:\(0\leq x\leq 1\)。设 LP 的最优解为 \(x ^ *\),将 \(S_j\) 加入 \(I\) 当且仅当 \(x_j ^ *\geq \fr 1 f\),则 \(I\) 是集合覆盖且是 \(f\)-近似。
LP 的最优解显然不劣于 IP 的最优解。
线性规划
最小化 \(c ^ T x\),要求 \(Ax\geq b\) 且 \(x\geq 0\)。
LP 算法的复杂度,其中 \(L\) 是输入的比特数:
- Simplex:指数,但很快。
- Ellipsoid:\(\tio (n ^ 6L ^ 2)\)。
- Interior Point:\(\tio(n ^ {3.5}L ^ 2)\)。
Open Problem:是否存在强多项式的算法。
对偶:最大化 \(b ^ Ty\),要求 \(A ^ Ty\leq c\) 且 \(y\geq 0\)。
根据 \(y_i\geq 0\) 得
即
而
所以
LP-duality Theorem
若原始线性规划问题的最优解空间有限,则其对偶问题的最优解空间有限。若 \(x ^ *\) 和 \(y ^ *\) 最优,则
证明略。
Complementary Slackness Conditions(互补松弛条件)
\(x, y\) 最优当且仅当:
根据 \(\sum_{j = 1} ^ n x_jc_j\geq \sum_{i = 1} ^ m y_ib_i\) 的证明过程和 LP-duality Theorem 易得。如果条件不满足,自然无法取等。
对集合覆盖,\(x_j = 1\implies \sum_{i = 1} ^ m a_{ij}y_i = c_j\),所以每次选一个 \(y_e\) 增加,直到满足某个 \(\sum_{e\in S_k} y_e = c(S_k)\),此时将 \(S_k\) 加入 \(I\)。这样每个点都被覆盖,否则其对应的 \(y_e\) 还能增加。同时解为 \(f\)-近似:
这是纯组合的算法,无需求解 LP。