动态图算法(中)

又是一天赶完真刺激……写的可能有点乱七八糟的不过原来的论文还是非常清楚的看的我非常感动

原文:Poly-Logarithmic Deterministic Fully-Dynamic Algorithms

for Connectivity, Minimum Spanning Tree, 2-Edge,

and Biconnectivity

作业进度太慢了是时候往前赶(ge)赶(ge)了(ge)


离线

分治

补充一下前文提到的分治做法,以询问双连通性为例。

按时间分治,把每条边表示成 [insTime,delTime]

solve(l,r) 处把所有包含整个 [l,r] 区间的边加入,只保留和 [l,r] 有交的边和询问的端点,其余点缩掉。这里的复杂度是 O(|V|+|E|) 的。

每个标记对时间复杂度的贡献是 O(\log n) 的,所以总时间复杂度就是 O(n\log n)

蜜汁做法

介绍一个迷之复杂度的做法。

继续用 LCT 维护关于删除时间的最大生成树。我们所遇到的最大问题是在替换一条边的时候非树边所覆盖的路径可能会产生变化。我们注意到,在 a 替换 b 的时候:

  1. 任何覆盖 b 的非树边 c 都会先于 b 被删除
  2. b 被删除前 b 所覆盖的路径都是非桥边
  3. a 替换 b 只会改变 b 所覆盖路径的桥边情况

于是我们的做法就是,覆盖的时候打时间标记,替换的时候直接替换并用 b 打一个覆盖标记,删除的时候试图把过期的标记回收上来。

我暂时还不会证明这个做法的时间复杂度,如果您会的话求告知!

注:这个做法时间复杂度不对的地方是,当我们需要询问整个图(或者一条链上)有多少桥边的时候(之前的做法是可以的),因为需要维护正确的子树信息所以需要复杂度不一定对的标记回收。而当我们只需要询问某一条边是否是桥边的时候,这个做法是正确的。


在线 - 桥

理论

我们仍然按照与之前类似的方法处理,我们赋予每条非树边等级 l(e) ,对于每条树边,我们记录覆盖它的非树边的最大等级 c(e)

G_il(e)\geqslant i 的非树边和所有树边组成的图。

path(x,y) 为生成树中 xy 的路径, meet(x,y,z) 表示 x 走到 path(y,z) 上的最短路的终点。


加边-树边:直接加入,令 c(e):=-1

加边-非树边:初始等级为 0,调用 cover(u,v,0)

cover(u,v,i)\forall e\in path(v,w)c(e)<i ,令 c(e):=i

删边-树边:调用 swap(e) ,转化为删除非树边。

swap(e) :令 e' 为覆盖 e 的等级最高的非树边,令 l(e')=r ,将 e 变为非树边,令 l(e):=r ,将 e' 变为树边,令 c(e'):=r

可以交换 ee' 是由于在 G_0,\ldots G_ree' 在操作前后都会出现,而 G_{r+1},G_{r+2},\ldotsee' 谁出现不会改变双连通性。

于是我们在切换后只需要删掉 e 并修复 path(e) 上的 c 即可。(事实上 swap(e) 还是会破坏原来经过 e 的非树边在 path(e) 上的覆盖情况)

删边-非树边:调用 uncover(u,v,l(e)),recover(u,v,l(e))

uncover(u,v,i)\forall e\in path(v,w)c(e)\leqslant i ,令 c(e):=-1

recover(u,v,i) :在 uv 路径上修复 c(e)\leqslant i 的值(准确来说是将过低的 c(e) 调到正确的值)。


只需要修复 c(e)=i 的然后递归下去,假设有等级为 i 的非树边 (x,y)path(x,y)\cap path(u,v) 上的覆盖需要修复,那么 \forall e\in path(meet(x,u,v),x),l(e)\geqslant ipath(u,v) 外的 c(e) 并不会出问题)。


因此我们在 path(u,v) 上按顺序枚举点 w ,寻找等级为 imeet(x,u,v)=w\forall e\in path(w,x),l(e)\geqslant i 的边 ,如果将 (x,y) 的等级提到 i+1 仍能使每个边双的大小不大于 \lceil n/2^{i+1}\rceil ,那么把它的等级改为 i+1 并继续,否则退出。然后倒过来改为枚举 path(v,u) 上的点 w 再做一次。

对正确性的证明:如果第一遍不停下显然是对的,否则令第一次停下的边是 (x_1,y_1) 当时枚举到 w_1 ,第二次停下的边是 (x_2,y_2) 当时枚举到 w_2 ,而唯一可能出问题的地方是 path(w_1,w_2) 上的边。之前显然 x_1,y_1,x_2,y_2G_i 的同一个边双里,设 (x_1,y_1) 连接两个边双后形成的边双为 C_1(x_2,y_2) 连接后形成的是 C_2 ,由于在这两条边处停下了所以 |C_1|+|C_2|> \lceil n/2^i\rceil ,而整个 x_1,y_1,x_2,y_2 所在的边双也不会超过这个大小,这说明 C_1,C_2 有交集,从而可以说明在调用 cover(x_1,y_1,i),cover(x_2,y_2,i) 后应当已经可以完全覆盖 path(w_1,w_2) ,从而说明了算法的正确性。


实现

cover和uncover都是不太难的覆盖标记,最麻烦的是recover需要支持的操作。


维护从每个点出发走 c(e)\geqslant i 的边能到达的点的个数 size_i(u)

维护从每个点出发走 c(e)\geqslant i 的边能到达的 c(e)=i 的边的个数 incident_i(u)

寻找 (x,y) :如果 incident_i(w)=0 就退出,找的时候向四个儿子递归。


|C_1|+|C_2| :把 path(x,y) 作为重链提出来,询问 size_i(x)


时间复杂度:每个点上要维护 O(\log n) 的信息(size,incident),因此 top tree 操作基本是 O(\log^2 n) 的,而每条边的等级最大是 O(\log n) ,复杂度 O(\log^3 n)

文章被以下专栏收录