一、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