Tarjan算法 割点和桥

一、Tarjan求割点

对于无向图G,如果删除某个点x后,联通分量数目增加,则称点x是图G的割点。

如何求割点呢?一种简单的方法是采取枚举每个点,删除后用DFS求连通分量,这样时间复杂度是O(nm),显然不很优。

我们把在结点a为根的子树(不包括a)的点集即为 S(a),把不在a为根的子树中的点集即为 T(a)。

  

如图,如果将点2删除,那么点4可通过S(2)中的返祖边和T(2)连通,删除2对它没有影响;但S(2)中的点5,由于点5没有返祖边,因此和T(2)不连通,即点2是割点。

总结一下,得到如下结论:

对于某点x,若S(x)中存在至少一点y,满足y与T(x)之间没有任何边,则x是割点。

具体实现上,我们可以利用Tarjan算法记录dfn[x]和low[x]。于是问题转化成,结点x是否存在一个儿子y,使得low[y]≥dfn[x]。我们只需一次DFS即可,总时间复杂度O(n+m)。

注意:对根必须单判,根要有至少两个子树才能算作割点。

参考程序如下:

 1 #include<bits/stdc++.h>
 2 #define int long long
 3 using namespace std;
 4 const int N=1e5+5;
 5 int n,m,x,y,cnt=1,hd[N],to[N<<1],nxt[N<<1],dfn[N],low[N],num,root;
 6 bool g[N];
 7 void add(int x,int y){
 8     to[++cnt]=y,nxt[cnt]=hd[x],hd[x]=cnt;
 9 }
10 void tarjan(int x){
11     dfn[x]=low[x]=++num;
12     int flag=0;
13     for(int i=hd[x];i;i=nxt[i]){
14         int y=to[i];
15         if(!dfn[y]){
16             tarjan(y),low[x]=min(low[x],low[y]);
17             if(low[y]>=dfn[x]){
18                 flag++;
19                 if(x!=root||flag>1) g[x]=1;
20             }
21         }
22         else low[x]=min(low[x],dfn[y]);
23     }
24 }
25 signed main(){
26     //freopen(".in","r",stdin);
27     //freopen(".out","w",stdout);
28     scanf("%lld%lld",&n,&m);
29     for(int i=1;i<=m;i++){
30         scanf("%lld%lld",&x,&y);
31         if(x==y) continue;
32         add(x,y),add(y,x);
33     }
34     for(int i=1;i<=n;i++)
35         if(!dfn[i]) root=i,tarjan(i);
36     for(int i=1;i<=n;i++)
37         if(g[i]) printf("%lld ",i);    //输出的是割点 
38     return 0;
39 } 

二、Tarjan求割边

桥(割边):无向连通图中,去掉一条边,图中的连通分量数增加,则这条边,称为桥或者割边。

判断桥:一条无向边(x,y)是桥,当且仅当(x,y)为树枝边,且满足 dfn(x)<low(y)。(因为y想要到达x的父亲必须经过(x,y)这条边,所以删去这条边,图不连通)。

因为遍历的是无向图,所以从每个点x出发,总能访问到它的父节点fa。根据low的计算方法,(x,fa)属于搜索树上的边,且fa不是x的子节点,故不能用fa的时间戳来更新low[x]。

如果仅记录每个节点的父节点,会无法处理重边的情况。当x与fa之间有多条边时,(x,fa)一定不是桥。在这些重复的边中,只有一条算是“搜索树上的边”,其他的几条都不算。故有重边时,dfn[fa]能用来更新low[x]。

改为记录“递归进入每个节点的边的编号”。编号可认为是边在邻接表中存储的下标位置。把无向图的每一条边看做双向边,成对存储在下标里。若沿着编号为i的边递归进入了节点x,则忽略从x出发的编号i xor 1的边,通过其他边计算low[x]即可。

 1 #include<bits/stdc++.h>
 2 #define int long long
 3 using namespace std;
 4 const int N=1e5+5;
 5 int n,m,x,y,cnt=1,hd[N],to[N<<1],nxt[N<<1],dfn[N],low[N],num;
 6 bool g[N<<1];
 7 void add(int x,int y){
 8     to[++cnt]=y,nxt[cnt]=hd[x],hd[x]=cnt;
 9 }
10 void tarjan(int x,int fa){
11     dfn[x]=low[x]=++num;
12     for(int i=hd[x];i;i=nxt[i]){
13         int y=to[i];
14         if(!dfn[y]){
15             tarjan(y,i);
16             low[x]=min(low[x],low[y]);
17             if(low[y]>dfn[x]) g[i]=g[i^1]=1;
18         }
19         else if(i!=(fa^1)) low[x]=min(low[x],dfn[y]);
20     }
21 }
22 signed main(){
23     //freopen(".in","r",stdin);
24     //freopen(".out","w",stdout);
25     scanf("%lld%lld",&n,&m);
26     for(int i=1;i<=m;i++){
27         scanf("%lld%lld",&x,&y);
28         add(x,y),add(y,x);
29     }
30     for(int i=1;i<=n;i++)
31         if(!dfn[i]) tarjan(i,0);
32     for(int i=2;i<cnt;i+=2)
33         if(g[i]) printf("%lld %lld\n",to[i^1],to[i]);
34     return 0;
35 } 

 三、例题

占坑qwq

猜你喜欢

转载自www.cnblogs.com/maoyiting/p/12603018.html
今日推荐