zghtyarecrenj
2020-02-28 14:48:09
窝看到仅有的一篇暴力的题解就生气……这个暴力很脆弱,随便卡就可以卡掉。
前置芝士:ETT(如果你不会,请自行百度,作者会日后写一个学习笔记的,到时候链接附在这里)
首先考虑我们用什么来维护一个图,把一个图变成一棵树。
那自然可以想到维护生成树。由于可能不太连通,所以我们维护一个生成树的森林。
加边操作不太难,直接在 ETT 里面 link 一下即可。询问也不太难,在 ETT 里面写一个 findroot 函数即可。
但是遇到删边操作的时候,直接处理是不可行的。因为如果在生成树中删掉一条边,我们需要枚举不在生成树中的边看一下是否可以使得删边之后不连通,那时间复杂度就挂了。
如下图,如果删去边
那么我们怎么办呢?
考虑建立一个类似分层图的模型。每条边的层不会增加,只会减小。
我们计最高层为
令
由层数单调递减可知
接下来的两个性质保证了算法的复杂度为
【性质1】
【性质2】我们令
所以
由此可以得出一个有趣的结论:
接下来看操作
表示 Graph
表示 std::set
的数组,作用类似邻接表。
F[ i ]
表示
上代码~(惊不惊喜,是伪代码!)
Graph[ v ]. insert( e ), Graph[ w ]. insert( e )
e. level = log n
if ( ! F[ log n ]. query( e ) )
F[ log n ]. insert( e )
不用解释。
return F[ log n ]. findrt( v ) == F[ log n ]. findrt( w )
实际上的程序比这个复杂……
Graph[ v ]. erase( e ), Graph[ w ]. erase( e )
if ( e \in F[ log n ] )
for i = e. level to log n do
F[ i ]. delete( e )
for i = e. level to log n do
令 Tv = F[ i ] 中有 v 的那棵树,Tw = F[ i ] 中有 w 的那棵树
if ( Tv. size > Tw.size )
swap( Tv, Tw ) // 你可以理解 Tv 与 T_w 为指针,所以可以交换。
for each 第 i 层中的边 e' = ( x, y )
if ( x \in F[ v ] )
if ( y \in Tw )
F[ i ]. insert( e' )
return
else
if (y \in Tb )
e'. level = i - 1
来解释一下为什么中间一些东西是对的。
因为
所以
因为
所以
这个东西的复杂度是