割点和桥 学习笔记

写在前面

以下内容均适用于无向图。

基本概念

割点:删去后原图不连通的点。

桥:删去后原图不连通的边。

点双连通图:删去任意一点仍连通的图。

边双连通图:删去任意一边仍连通的图。

双连通分量:图的极大双连通子图。

求割点与桥:Tarjan算法

割点判定法则

一个顶点是割点的充要条件为以下两个之一:

  • 该点是树根,且该点有多于一个子树。

  • 该点 $u$ 不是树根,且满足存在一个儿子 $v$ 使得 $DFN(u) \le Low(v)$。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void dfs(int u,int fa){
dfn[u]=low[u]=++tim;
fl[u]=1;
int sz=0;
for(int i=lst[u];i;i=e[i].lst){
int v=e[i].t;
if(!fl[v]){
dfs(v,u);
sz++;
if(u==root&&sz>1)cut[u]=1;
low[u]=min(low[u],low[v]);
if(u!=root&&dfn[u]<=low[v])cut[u]=1;
}
else{
low[u]=min(low[u],dfn[v]);//无向图,不用栈
}
}
if(cut[u])tot++;
}
void tarj(){
for(int i=1;i<=n;i++){
if(!fl[i])root=i,dfs(i,0);
}
}

桥判定法则

一条边是桥的充要条件是:

  • 搜索树上存在 $u$ 的一个儿子 $v$ 满足 $DFN(u) < Low(v)$。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void dfs(int u,int pre){
dfn[u]=low[u]=++tim;
fl[u]=1;
for(int i=lst[u];i;i=e[i].lst){
int v=e[i].t;
if(!fl[v]){
dfs(v,i);
low[u]=min(low[u],low[v]);
if(dfn[u]<low[v]){
e[i].bridge=e[i^1].bridge=1;
tot++;
fe[tot]=e[i];
if(fe[tot].f>fe[tot].t)swap(fe[tot].f,fe[tot].t);
}
}
else if(i!=(pre^1)){//对重边进行特判
low[u]=min(low[u],dfn[v]);
}
}
}
void tarj(){
for(int i=1;i<=n;i++){
if(!fl[i])dfs(i,0);
}
}

求双连通分量

边双连通分量的求法

求出所有桥后,把桥边删除,原图变成多个连通块,每个连通块都是一个边双连通分量。具体实现可以DFS一遍,遇到桥就跳过。

代码实现

1
2
3
4
5
6
7
8
9
10
vector<int> ans;
void dfs2(int u){
fl[u]=1;
ans.push_back(u);
for(int i=lst[u];i;i=e[i].lst){
int v=e[i].t;
if(fl[v]||e[i].brg)continue;
dfs2(v);
}
}

点双连通分量的求法

效仿强连通分量,维护一个栈。第一次访问节点时入栈。当割点判定法则中 $DFN(u) \le Low(v)$ 成立时,无论该点是否为根,都要从栈顶不断弹出节点直至 $v$ 出栈,此时弹出的所有结点与 $u$ 一起构成一个点双连通分量。注意:孤立点也属于点双连通分量!要特判!

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void dfs(int u){
fl[u]=1;
dfn[u]=low[u]=++tim;
stk[++top]=u;
int sz=0;
for(int i=lst[u];i;i=e[i].lst){
int v=e[i].t;
if(!fl[v]){
dfs(v);
sz++;
low[u]=min(low[u],low[v]);
if(u==rt&&sz>1)ct[u]=1;
if(u!=rt&&dfn[u]<=low[v])ct[u]=1;
if(dfn[u]<=low[v]){
hd[v]=1;
while(stk[top]!=v){
ans[v].push_back(stk[top]);
vis[stk[top]]=1;
top--;
}
ans[v].push_back(v);
ans[v].push_back(u);
vis[u]=vis[v]=1;
top--;
}
}
else{
low[u]=min(dfn[v],low[u]);
}
}
}
void tarjan(){
for(int i=1;i<=n;i++){
if(!fl[i])rt=i,dfs(i);
}
}