基本图论-连通分量(强/弱联通 割点/边 边/点双)
y2823774827y · · 个人记录
前言
网上现存
-
本文涉及强连通分量、弱连通分量、割点、割边、边双、点双,属于基本图论范畴
-
在有着直接关联的基础上又有所不同,本文基于把抽象的数组转换为在图上的意义,旨在让初学者能更轻松地理解并区分差别
-
为避免各个板子的差别过大,在正确的基础上尽量保证代码的相似性
如果您之前学过,可能与您的定义有所不同,故请在看完每个算法下面的代码后再进行文字阅读
-
文字中某个词语后出现带圆框的数字,如①②,这些词语将会在文字下方有详细的注释,方便阅读
前置
我们简略地定义
如下图及
有向图
强连通分量
定义:有向图中某个点集中的点互相能到达的分量为强连通分量
为方便理解我们采取归纳法:找到完整强连通分量后立即染色
- 定义
dfn_u :表示dfs 中u 的时间戳;初始化为第几个被遍历到的点。 - 定义
low_u :表示u 能到达且在u 子树边界的未染色的最小时间戳①( 设代表该最小时间戳点的点为x ,可证明x 一定能与u 组成强连通分量②) ;初始化为dfn_u 。
①:显然代表该最小时间戳的不为
u 的子树( 除u) ,因为子树内的时间戳u 已经为最小的了。故u 的子树并不影响low_u ,真正影响的是u 的子树外,与u 子树有接触,且未染色的。②:
( 下图为例)x 位于f 的左子树,x 所在完整强连通内所有节点不止在左子树( 否则就染色了) ,x 至少能与f 组成强连通分量。故x 一定能与u 组成强连通分量:f\rightarrow u\rightarrow x\rightarrow f 。
具体做法:在
code
也可更换第
else if(visit[v]) low[u]=std::min(low[u],low[v]);
//此时定义low:能与u组成强连通分量(未染色)的最小时间戳
void Tarjan(LL u){
dfn[u]=low[u]=++tim; sta[++top]=u; visit[u]=true;
for(LL i=head[u];i;i=dis[i].nxt){
LL v(dis[i].to);
if(!dfn[v]){
Tarjan(v); low[u]=std::min(low[u],low[v]);
}else if(visit[v]) low[u]=std::min(low[u],dfn[v]);
}
if(low[u]==dfn[u]){
LL now; ++nod;
do{
now=sta[top--]; col[now]=nod; visit[now]=false;③
}while(now!=u);
}
}
③:
visit 清空:强连通分量是建立在有向图上的,u\rightarrow v,v\nrightarrow u 时,如果之前先遍历过v ,则已经把visit 清空了,此时u 不受v 的任何影响
弱连通分量
定义:同一弱连通分量里的任意两个点
想象一下某个弱连通分量进行强连通缩点后的样子?能两两到达的肯定存在于同一个大点中了,剩下的肯定是单向联通,故一定是一条单直链
性质:某一点可能属于多个弱连通分量,显然,属于强连通分量的两点一定属于同一弱连通分量
做法:在强连通缩点后的
无向图
为了割点与桥的统一计算,在无向图中我们不管父亲
且重新定义:
- 定义
low_u :u 的子树与子树外接触的最小时间戳( 除(u,f) 的影响,因为在遍历u 时遇到f 会跳过)
如下图,蓝边为
割点
定义:无向图中,将该点从原图中拿掉后,连通分量数量增加
想象一下割点在图上的样子:一个点至少夹在两个互不接触 (( 不考虑该点的连接作用 )) 联通块之间
为了便于理解,先想一下暴力做法④:特判每一个点,如果该点至少有两个儿子则说明为割点
④:因为判断儿子得把整个子树全跑一遍。而每一次判断的儿子个数仅对根有效。因为根肯定得把一个子树的点遍历完才能回溯;其他的点由于顺序关系,入度也会成为一个儿子
( 对于无根树) ,实际上入度上方可能与儿子有接触,故不能一次性判断。比如下面的这幅图,从1 开始遍历则3 有两个儿子;但从3(root) 开始遍历:3\longrightarrow 2\longrightarrow 1\longrightarrow 12\longrightarrow 13\longrightarrow9\longrightarrow8\longrightarrow6\longrightarrow7\longrightarrow5\longrightarrow4\longrightarrow10\longrightarrow11 ,最后得到的是3 只有一个儿子,所以3 不为割点
转换成条件:对于
抽象成代码:
细节:如果我们首先遍历
⑤:有时我们发现根不会为割点,这是为什么呢?因为
u 的子树就是所有点,故没有外界,也就是说特判一定满足,故该特判对其无效。所以需要判断的是
u 是否有至少两个儿子( 原理就是上面的暴力做法) ,否则就为无根树上的叶子节点了( 也就是边界)
code
void Tarjan(LL u,LL mr,LL f){
LL rc(0);
dfn[u]=low[u]=++cnt;
for(LL i=head[u];i;i=dis[i].nxt){
LL v(dis[i].to);
if(v==f) continue;
if(!dfn[v]){
Tarjan(v,mr,u);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]&&u!=mr)
cut[u]=true;
if(u==mr)
rc++;
}else low[u]=min(low[u],dfn[v]);
}
if(u==mr&&rc>=2)
cut[mr]=true;
}
桥
定义:又称为割边,将该边从原图中拿掉后,连通分量数量增加
想象一下桥在图上的样子:一条边被两个不接触
如下方,两个"
为了便于理解,先想一下暴力做法:枚举每一条边
转换成条件:对于桥
抽象成代码:
void Tarjan(LL u,LL f){
dfn[u]=low[u]=++cnt;
for(LL i=head[u];i;i=dis[i].nxt){
LL v(dis[i].to);
if(v==f) continue;
if(!dfn[v]){
Tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u]){
e[++tot][0]=u; e[tot][1]=v;
}
}else low[u]=min(low[u],dfn[v]);
}
}
其实割点是可以考虑父亲的影响,而桥绝对不能
因为
而桥
故为了代码的方便,在割点割边时不考虑父亲
边双连通分量
定义:简写为边双,同一边双内,点与点的边集中无桥
如下图,每种颜色的点为一个边双,之间由桥隔开
具体做法
-
两次遍历:这个就比较简单了,直接找出所有的桥删掉,然后遍历一遍染色就行了,因为桥已经被全部删掉,故每种颜色的分量的边集中肯定无桥
-
一次遍历:桥的定义入手,考虑桥
(f,u) ,u 的子树局限于内部,故满足low_u=dfn_u ;而同属u 的边双内的任意点x ,由于无桥,肯定不会局限于x 的子树,故满足low_x≠dfn_x 。与强连通分量的做法类似,判断dfn_u=low_u ,把压进栈里的点取出来染色即可
void Tarjan(LL u,LL fa){
dfn[u]=low[u]=++tim; sta[++top]=u;
for(LL i=head[u];i;i=dis[i].nxt){
LL v(dis[i].to);
if(v==fa) continue;
if(!dfn[v]){
Tarjan(v,u); low[u]=std::min(low[u],low[v]);
}else low[u]=std::min(low[u],dfn[v]);
}
if(low[u]==dfn[u]){
LL now; ++nod;
do{
now=sta[top--]; col[now]=nod;
}while(now!=u);
}
}
点双连通分量
定义:简写为点双,对于同属一个点双的任意点,删除后,该分量中的点仍能互相到达;或者说仅对于该分量而言,无割点。
具体做法:
依旧从割点的定义入手:割点将原图分成互不相连的多个联通块,显然每个联通块本身已经是一个点双了,但不完整,因为相邻的割点在边界,如果与联通块共同组成一个新联通块,割掉后也不会另外产生联通块
我们怎么染色呢?可以发现在
新建一个节点来维护某个点双,该点向该点双的每个点连一条边
⑥:如下图
void Tarjan(LL u){
dfn[u]=low[u]=++tim; sta[++sta[0]]=u;
for(LL i=G1.head[u];i;i=G1.dis[i].next){
LL v(G1.dis[i].to);
if(!dfn[v]){
Tarjan(v);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]){
G2.Add(++tot,u); LL now(0);
do{
now=sta[sta[0]--];
G2.Add(tot,now);
}while(now!=v);
}
}else low[u]=min(low[u],dfn[v]);
}
}
例题一:[HNOI2012]矿场搭建
我们把每个点双看作一个分量
-
分量无割点:说明整个联通块就是一个点双,建两个出口,随便割一个由于点双的性质所有点都能出去
-
分量有一个割点:在除割点的地方建一个出口,割掉割点直接去分量里的出口,割掉出口通过割点跑到其他分量的出口中
再具体点就相当于多个点双构成了一棵树
例题二:[POI2008]BLO-Blockade
显然仅割点会对除本身以外的访问有影响,影响为多个分支所跨该割点访问数,
记分支的节点数分别为
例题三:[ZJOI2004]嗅探器
其实就是求:多个点双构成的树,
-
首先得所属节点不同:
low_y>=dfn_x -
其次得保证
u 是位于期间的割点:dfn_u<=low_v -
且
u 的该分支v ,y 存在于子树v 内:dfn_v<=dfn_y
例题四:HDU - 5215
纯奇环偶环通过dfs树上,染色判断(由于偶环可能有两个奇环,通过一点相交,dfs树上并不能判完)
两环如果相交必定形成偶环,由于不可以重复经过边,把每个边双提出来判断一下是否存在两个环以上即可
code