连通分量相关算法学习笔记

WARNING:本文存在大量口胡,请谨慎食用

 

参考:刘汝佳《算法竞赛入门经典——训练指南》

似乎一直没真正理解这些东西,于是apio考场上根本写不出来(当然写出来了其实也不会)。于是再学习一发。

dfs树

即在图上dfs所得的树。由于是图上遍历会有一些非树边。无向图中仅包含树边和反向边(树枝间不会有连边否则会并为一个),有向图含树边、反向边、前向边、交叉边。

割顶

无向图中,删除该点后连通分量数量增加。

无向图中,删除该边后连通分量数量增加。

割顶求法

tarjan。对图dfs。对于树根特判,若有多个儿子则根为割点。每个点记录dfn(时间戳)和low(后代所能连回的最早的祖先的dfn)。即low[x]=min(min{low[y]}((x,y)∈G,y is x's son),min{dfn[y]}((x,y)∈G,y is x's ancestor))。设点x的儿子为y,则若low[y]>=dfn[x],x为割点。

1.正确性 显然dfn大于该点的点一定是该点的后代或其他分支上的。该点的后代并不会连向其它分支,所以去掉了这个点他的某些儿子的后代就没法和其他部分连通了。

2.所连向的点被访问过时,low[x]=min(low[x],low[y])挂的原因 设t为x祖先中的一个割点。若y为t祖先或后代,取dfn和low效果都是一样的;但y为t时,若取low[t],此时包含了t的儿子中其它已访问分支的反向边。但显然去掉t后x是无法走到这个边上的。

桥求法

类似。设点x的儿子为y,则若low[y]>dfn[x],边(x,y)为桥。

有重边时注意是否访问父节点。

从割点的分析看,对于桥这个地方取dfn或low似乎并没有影响,对拍了一下好像也没毛病,那应该就是都可以吧。

由判断条件,桥的要求比割点更严格。有割点不一定有桥。桥的两顶点均为割点(去掉桥后变成孤立点除外)。依然看dfs树的话,显然父亲节点肯定是割点。儿子的后代至多能连接到儿子,则儿子为割点。

边双连通分量

边双连通的极大子图。边双连通,即无向图中任意两点间有至少两条边不重复的路径,也即图中没有桥。等价性显然。

同时等价于任意边至少存在于一个简单环内。必要性:否则去掉此边,其两顶点不再联通。充分性:显然环上边不会是桥。

点双连通分量(双连通分量)(BCC)

点双连通的极大子图。点双连通,即无向图中任意两点间有至少两条点不重复的路径(当然不包括起点终点,两点一线满足该性质),也即图中没有割点。等价性显然。

证一些结论。

点双的定义等价于任意两边在同一简单环内。必要性:若某两边不在同一简单环内,则两条边之间的连接是一条链套上一堆环,且这两条边各自所在的环没有交边(否则可以并为一个更大的环)。那么显然链上存在割点。充分性:显然去掉点至多只会在环上删去相邻两条边,对连通性无影响。

点双内任意点到另任意两点均有点不相交路径(不包括起点终点)。假设该点为c,另两点为a,b(不考虑特殊的平凡情况)。假设x和y为c到a的两条点不相交路径,b到c的某条路径(不经过a,显然这样的路径存在,否则a为割点)中与x和y的第一个交点为z(若没有交点显然成立)。不妨令z在x上。则c→z与z→b均与y无交点,该c到b的路径也与y无交点,得证。

点双内相邻两点到另任意两点均有点不相交路径(不包括起点终点)。假设相邻两点为c,d,另两点为a,b(不考虑特殊的平凡情况)设x和y为c到a和b的两条点不相交路径(若经过d则显然),w为d到a的一条路径(与x无交点,包括c)。若w与y无交点,则显然w和y满足条件;否则设w和y的第一个交点为z,则d→z→b与x无交点,得证。

显然由上可以得出,点双内任选两点,其间简单路径的并为整个点双(包括点和边)。

两点一线是点双而不是边双,这样看起来把点双简称为双连通分量有点不靠谱……不过不考虑特殊情况,点双一定是边双。

边双求法

求出所有的桥然后直接dfs,不经过桥就可以了。

点双求法

因为割点需要被统计多次,不能直接乱搞了。

依然考虑dfs树。可以发现点双就是一个割点能割开的儿子的子树中去除同为割点的后代的子树。于是考虑用栈记录树边(以保留割点),在发现某割点的儿子会被割开时弹栈。这样一个割点的子树都处理完时,会被割开的后代已经出栈被记录为BCC,而不会被割开的留下来,继续在其他分支中寻找并合并为BCC。或者直接记录点,弹到割点的时候再加一个进去。

强连通分量(SCC)

强连通的极大子图。强连通,即有向图中任意两点相互可达。

强连通分量求法

还是考虑dfs树。有向图中会出现前向边和交叉边。前向边即由祖先向已访问过的后代的边,交叉边即无祖先后代关系的点之间的边。两棵子树间交叉边的方向一定是相同的,否则显然不会形成两棵子树。

与求BCC类似,当第一次访问到一个SCC中的点时,类似于割点,该SCC中其他点都在以这个点为根的子树中,且形成一个连通块。略微思考就可以证明。于是只需找出第一个被访问的点。

同样记录dfn和low。low是某点通过其自身或后代所能追溯到的在同一SCC内的最早的点。若某点返回时dfn=low,则该点为该SCC第一个被访问的点。同样用栈记录点,出现dfn=low时不断出栈弹出整个SCC,也即直到弹出第一个被访问的点。

仍考虑正确性和非树边low写法会不会挂。

在计算low时,我们原则上仅对所到达点能够返回的取min,因为这样才是在同一SCC内。当然也只是原则上。对于树边,我们啥都不管直接对其儿子的low取min。如果儿子本身就和其在同一SCC内固然好,但若不在,这也并不会产生影响,因为这样的话儿子一定是SCC中第一个被访问的点,dfn比该点自身大。对于反向边,显然是能够通过树边返回的。前向边没有考虑的必要,因为指向的就是子树内的点。交叉边需要分两种情况,如果指向点当前仍在未确定SCC归属的栈中,应当对其取min,因为这说明该指向点可以通过交叉边反向边的方式到达它们的lca,否则其所在SCC应该已被弹出了。同理如果已确定SCC,就不能对其取min。我们tarjan的过程显然考虑了这一点。

可以知道若x的low对y进行了一次有效取min,则x和y一定在同一SCC内。所以无论是用dfn还是low更新,都是位于同一SCC中的值,不会有影响。

应用

缩点

SCC缩点得DAG(显然无环)

边双缩点得树(显然无环)

2-SAT

建边不说了。建好后求出来SCC,考虑每个变量。显然若真假在同一SCC里,2-SAT无解。否则,若其SCC拓扑序存在先后关系,必然选择靠后的,否则矛盾。若无先后关系,由于2-SAT建边各种神奇的性质(对称)(并不懂),任取一个都是可行的。由tarjan的过程可知,先被找出的SCC一定是拓扑序靠后的。于是可以直接比较SCC时间戳的大小判断选哪个。

圆方树

参考:immortalCO WC2018课件

原图的点为圆点。对每个点双新建一个方点,由方点向该点双中所有点连边,并去除原图中的边,得到圆方树。方点在树中即表示一个点双。

为什么是树?点双内显然无环,而点双间的连接点只有割点,这样仍是类似于原dfs树的结构。

显然树上圆方点相间。两方点间的圆点,也即非叶圆点都是割点,而叶圆点不是。由之前的结论,两圆点间的路径包含的方点对应点双即为两点简单路径可经过的所有点,圆点即所有必经点(割点)。

posted @ 2018-07-27 18:30  Gloid  阅读(893)  评论(0编辑  收藏  举报