动态图算法 (上)

一鸽就是一个月啊……最近事情也挺多的再加上看了一段时间的线代和ML,也没怎么看paper了。<del>暂且用这个垫着吧。</del>


原文:G. N. Frederickson. Data structures for on-line updating of minimum span-

ning trees, with applications. SIAM J. Comput., 14:781 - 798, 1985.


看着看着paper发现又看到冬令营讲过的内容了,偷了个懒(然而写起来还是累死了)就先说这个吧qaq。


这里考虑三个问题:连通性,最小生成树 (MST) 和双连通性。

支持的操作有:加边,删边(MST 里还有修改边权)。


离线部分


连通性可以维护以删除时间为权值的最大生成树。 O(n\log n)

MST我还没想到什么更好的做法,线段树分治+LCT应该可以。 O(n\log^2 n)

(话说当时看了好久的严格 O(\log n) 的LCT一直没有明白,什么时候我再去看看吧)

双连通性应该可以分治+并查集。 O(n\log^2n)


在线 - 连通性

我们考虑还是给边一个权值(为了不和MST中的权值混淆,下称其为等级),继续维护最大生成森林。

我们令 T_w 是生成森林中等级 \geqslant w 的边形成的森林。

插入一条新边时令其等级为0。

删除非树边是不会有问题的,删除树边的时候我们需要寻找一条非树边来替换。

我们考虑暴力枚举,当我们删除一条等级为 w 的边 (x,y) 时,就枚举 w-1 以下的等级,试图替换这条边。

假设枚举到等级 i ,我们在 T_i 中考虑,令删除边后树中 x 所在的连通块为 C_xy 所在的连通块为 C_y ,设 |C_x|\leqslant|C_y|

我们并没有什么很好的方法,所以只能暴力枚举 C_x 的每条边,由于只有 C_xC_y 的连通性发生了改变,所以这些边应该要么是 C_x 内部的,要么是 C_x 连向 C_y 的。只要找到一条属于后者的边我们就完成任务可以退掉,遇到前者我们就把它的等级+1。

这样的话时间复杂度就是 O(nr) ,其中 r 是最大等级。

我们来对我们的算法进行一些观察,来试图证明 r=O(\log n)

我们不难发现,在 T_{i+1} 中, C_x 必然是不会和其他连通块相连的,因为 T_{i+1}\subset T_i ,于是我们每次实际上都把 T_i 中的一个连通块的一半搬到了 T_{i+1} 去,从而证明了 T_i 的连通块大小不会超过 n/2^i ,从而证明了 r=O(\log n)

每一层的 T_i 可以用LCT维护,复杂度就是 O(n\log^2 n)


在线 - MST(sqrt m)

现在我们遇到非常棘手的问题了,维护MST是比维护连通性难很多的问题,不过我们可以退而求其次,试图在根号的时间内解决,也就是对MST分块。

我们面临的主要问题是:怎么分块,link/cut的时候怎么保证分块的性质,怎么处理非树边替换树边。

我们一个个问题来看,首先是如何分块,我们显然是不能直接分块的,于是我们可以用经典的左儿子右兄弟来处理这个图使其度数至多为3,然后就可以分成 O(m/z) 块,每块的大小是 O(z) 的。

Link/cut的时候保证性质也比较容易,因为我们是对大小分块的,只需要保证块大小是O(z)的即可,因此直接暴力dfs整个块进行merge/split即可。


现在我们遇到了最大的问题,怎么处理非树边替换树边的问题,暴力的话要暴力枚举用来替换的边 (x,y) 的端点所在的块。不暴力的做法的话,我们可以考虑对每个块用数据结构来维护它连到每个其他块的边的最小值。我们可能会进行的操作有:link/cut/询问一个连通块内的点权最小值(我们可以先cut掉要替换的树边,然后直接对另一个连通块查询)/修改点权。这个看起来可以用Top Tree来做,于是这样我们应该可以大概得到一个 O(m\sqrt{m}\log m) 左右的做法。

因为我们加入了额外的数据结构,于是我们来回顾merge和split的过程,我们注意到一个点的度数至多为3,所以我们其实还是可以暴力重建整个块的数据结构(Top Tree)。

为了不用 Top Tree 这种既复杂也慢的数据结构,也为了把那个log去掉,我们试着取寻找能替代 Top Tree 的数据结构,毕竟我们只需要询问整个连通块的点权最小值而不是子树的。我们可以继续考虑我们之前的分块,因为我们之前实际上是一棵 \sqrt{n} 叉树,我们能不能把它改成类似2叉树(实际上叫 Topology Tree ,下简称TT)的形式呢?我们可以取 z=2 ,分块以后把每个块缩成一个点,这样每次都会把点数折半,从而就使深度变成了 O(\log n) ,看起来问题就很顺利的解决了。

但等等!真的解决了么?我们需要保证的最大问题:度数至多为3这一点并没有被保证。或许我们可以把z取到4然后再故技重施把度数再变回3,但那样的话可能merge/split操作会变得令人非常痛苦(虽然我认为最终的解决方案还是很令人痛苦),而且也有失优雅。事实上我们有更好的做法:我们可以简单的在之前的限制中加入度数不超过3的限制,就可以轻松(真的么)的解决问题。分块的代码如下:

def findcluster(x):
	clust = [v]
	for v in child[x]:
		clust.append(findcluster(v))

	if len(clust) < Z && degree(clust) < 3:
		return clust
	else:
		print clust
		return []

我们分析一下块的数量,令度数为 i 的块数为 C_i 。由于度数不为 3 的块的大小一定达到了 z ,因此 C_1+C_2=O(m/z) 。然后我们可以列出方程:

\begin{equation} \left\{ \begin{aligned} C_1+C_2+C_3&=V \\C_1+2C_2+3C_3&=2V-2 \end{aligned} \right. \end{equation}

就可以简单的解得 C_1=C_3+2 ,从而证明了块数是 O(m/z) 的。(注:在这里有一件事情要说明,图中的每个点度数不超过3,树中的每个块度数不超过3,但是如果在图中看每个块,度数是没有保证的,这也是我们不能直接用这个方法分块暴力的原因)

尽管我们得到了可以进一步剖分的做法,然而由于我们在分块的时候对度数也加入了限制,merge/split的过程就会变得更加复杂。为了最后的做法,我们必须硬着头皮来分析。

Merge的时候,我们要把一棵TT插到另一棵下面,会使得某个TT节点的度数增加,从而整个到根路径上的点的度数都可能会增加,因此某些块的度数会从3变成4。不加证明的给出结论(由于点数较少应该可以分类讨论),我们可以把这个块拆成两个合法的新块,从而把问题丢给它的父亲。拆成新块可以重新用函数计算。(例子参见下图)





上面这么丑的图怎么可能是我画的哼


Split的时候,我们按照那条边把包含这条边的块切开,然后对于不合法的情况像Merge一样处理掉即可。(例子参见下图)



有了新的数据结构,link(合并两棵树)/cut(分裂开一棵树)/查询整个连通块的最小点权/修改点权(像线段树一样维护最小值)都能在 O(\log n) 的时间内解决了,而建树的复杂度是 O(n) 的(和线段树是一样的),我们就把复杂度推到了 O(m\sqrt{m\log m}) 上,然而我们还是没有去掉那个 log 。

我们考虑直接维护块之间的最小边,还是先对 z 分块,然后对缩点之后的树建TT。对于两个深度相同的 TT 节点 u,v ,我们开一个2D TT节点 D_{u,v} 来维护 uv 之间的最小边, D_{u,v} 的子节点是 \{D_{u',v'}|u'\in son_u,v'\in son_v\}

当 TT 的形态产生变化时,我们也要修改对应的 2D TT 。根据我们之前的分析, TT 的变化都是根到某个节点 x 的变化。那么我们需要修改的点数就是 \sum_{k=1}^{dep_x}(\text{Number of TT Nodes with depth $k$})\leqslant \text{Number of all TT Nodes}=O(m/k) 询问时即是询问 TT 中两个节点 uv 的最小边,令 u 是深度较大的点,暴力枚举 v 的子树中的与 u 深度相同的点 r ,取 \min\{val(D_{u,r})\} 即可,复杂度 O(m/z) 。最后令 z=\sqrt{n} 即可。

另:感觉直接发原论文的截图好像不太对劲……我自己待会去画一个好了……

文章被以下专栏收录