在之前的博客中我们已经介绍了如何用Tarjan算法求有向图中的强连通分量,而今天我们要谈的Tarjan求桥、割点,也是和上篇有博客有类似之处的。
关于桥和割点:
桥:在一个有向图中,如果删去一条边,而后这个有向图不再联通,我们便称删去的这条边为有向图的桥。
割点:在一个有向图中,如果删去一个点,使这个有向图中剩下的点不在联通,我们便称这个点为有向图的割点。
Tarjan算法原理分析:
和上文一样的,我们求出一个dfn数组(进行dfs时遍历的顺序),和一个low数组(以u为根的子树中,能连到dfn值最小的节点)。
求桥:
对于一个条边u,v(u,v为边的两个端点),如果dfn[u]<=low[v],那么这条边就是桥。因为以v为根的子树中无法通过返祖边(返祖边的定义可以参考我之前的博客)来连到u的祖先,只能通过u,v这条边才能与u的祖先联通,那么也就意味着将着条边删去后,以v为根的子树将不再能与其他点联通,所以这条边就是桥。
求割点:
对于一个点u:
1、如果它是根,那么假如它只有一个儿子,那么它就不是割点,假如有多个儿子,那么它就是割点。
2、如果它不是根,那么假如dfn[u]<=low[v],它就是割点。因为删掉点u以后,v以及v的子树不能到达u的祖先。
特别注意:
在无向图中,不一定需要返祖边来更新low数组,横叉边也一样可以。
代码:
var next,head,vet,a,dfn,low:array[1..200000]of longint; ans,i,n,m,x,y,tot,time,root:longint; function min(a,b:longint):longint; begin if a<b then exit(a) else exit(b); end; procedure add(x,y:longint); begin inc(tot); next[tot]:=head[x]; vet[tot]:=y; head[x]:=tot; end; procedure tarjan(u:longint); var i,v,cnt:longint; begin // inc(time); i:=head[u]; cnt:=0; dfn[u]:=time; low[u]:=time; while i<>0 do begin v:=vet[i]; if dfn[v]>0 then low[u]:=min(dfn[v],low[u]) else begin inc(cnt); tarjan(v); low[u]:=min(low[u],low[v]); if ((u<>root)and(dfn[u]<=low[v])or(u=root)and(cnt>1))and(a[u]=0) then begin inc(ans); a[u]:=1; end; end; i:=next[i]; end; end; begin read(n,m); for i:=1 to m do begin read(x,y); add(x,y); add(y,x); end; for i:=1 to n do begin root:=i; if dfn[i]=0 then tarjan(i); end; writeln(ans); for i:=1 to n do if a[i]=1 then write(i,' '); writeln; end.