Tarjan 是个著名的计算机科学家,他发明了不少算法,在求解图的连通性有关问题时,最著名的应该是割点割边和强连通份量。c++
在图中去掉这个点和它的全部直接连边,原来联通的图就不联通了,那它就是割点。算法
在图中去掉这条边,原来联通的图就不联通了,那它就是割边。数组
非连通图的全部连通块的割点(割边)集合的并是它的割点(割边)集合。ide
tarjan 算法核心在于 dfs,将图转化为 dfs 树,并记录 dfn(一种dfs序,又名时间戳)。spa
考虑对图 dfs,dfn[i] 表明 i 节点的访问次序。一次 dfs 下来,因为咱们有 if(vis[y])continue;
的判断,因此必然有一些边咱们没有走,那那些咱们走过的边就必然构成一棵 dfs 树,那些舍弃的边就是 dfs 树中的反向边。code
首先,考虑树根。只要树根有多于1个儿子,它就是割点递归
令 low[i] 表示节点 i 的子树中的节点经过各自的反向边(只走反向边)可以向上回溯到的dfn最小的节点。ci
想一想,\(low[y]\ge dfn[x]\) (y是x的儿子之一)是什么意思?就说明y子树中的节点没有跟x子树外部节点的有效连边。所以,x 就是一个割点。get
割边也很好找,由于反向边必然不是割边,所以只要 dfn[y]>x 那边\(x\leftrightarrow y\) 就是割边。且,这时不用特判树根it
#include<bits/stdc++.h> using namespace std; const int N=2e4+5; int dfn[N],low[N]; bool iscv[N],vis[N]; vector<int>G[N]; int ord=0,cnt=0; void dfs(int x,int rt){ dfn[x]=low[x]=++ord; for(int i=0;i<G[x].size();i++){ int y=G[x][i]; if(!dfn[y]){ dfs(y,rt); low[x]=min(low[x],low[y]); if(low[y]>=dfn[x])iscv[x]=1; if(x==rt) cnt++; } else low[x]=min(low[x],dfn[y]); } } int main(){ int n,m; cin>>n>>m; int u,v; for(int i=1;i<=m;i++){ cin>>u>>v; G[u].push_back(v); G[v].push_back(u); } for(int i=1;i<=n;i++) if(!dfn[i]){ cnt=0; dfs(i,i); if(cnt>1) iscv[i]=1; else iscv[i]=0; } int ans=0; for(int i=1;i<=n;i++) if(iscv[i]) ans++; cout<<ans<<endl; for(int i=1;i<=n;i++) if(iscv[i]) cout<<i<<' '; }
有向图的一个极大联通子图是它的一个强连通份量。
两个要点:极大,联通。联通好理解,份量中任意两点\(u\to v\) 两两可达。注意!有向图,所以仅仅u可达v,v却不可达u是不能够的;极大:若是一个强连通份量的子连通份量也是一个节点两两可达的子图,那它也不算一个强连通份量,由于还有比他更大且包含它的
代码其实和割点割边差不太多,作法含义有所不一样
一样有low,dfn两个数组,还有一个栈。到达一个点就把它入栈,查看它的全部儿子,若是不是反向边,就递归这个儿子,而后更新low[x]=min(low[x],low[y])。若是是反向边,注意,若是反向边的那一头是一个已经找到强连通份量的点,那就忽略这条边,不然,更新low[x]。一个要点是,只有尚未找到强连通份量的点才在栈中,段末有解答。
当咱们查看完全部x的儿子后,咱们判断x是否是强连通份量的根。他是一个强联通份量的根当且仅当此时还low[x]=dfn[x],那么这个强连通份量包含的节点就是x的子树中全部还没找到归属地的节点,这些节点哪里找,就在栈顶到栈中x所在位置的这一个区间,咱们把他们收拾起来,而后一一退栈(已经不须要解答了吧)
#include <bits/stdc++.h> using namespace std; const int N=1e4+5; int top,ord,Bcnt; int stk[N],instk[N],dfn[N],low[N]; vector<int>G[N],B[N]; void scc(int x){ dfn[x]=low[x]=++ord; stk[++top]=x; instk[x]=1; for(int i=0;i<G[x].size();i++){ int y=G[x][i]; if(!dfn[y]){ scc(y); low[x]=min(low[x],low[y]); } else if(instk[y]) low[x]=min(low[x],low[y]); } if(dfn[x]==low[x]){ Bcnt++; while(top){ instk[stk[top]]=0; B[x].push_back(stk[top]); top--; if(stk[top+1]==x)break; } sort(B[x].begin(),B[x].end()); if(x!=B[x].front()) B[B[x].front()]=B[x],B[x].clear(); } } int main() { int n,m,u,v; cin>>n>>m; for(int i=1;i<=m;i++){ cin>>u>>v; G[u].push_back(v); } for(int i=1;i<=n;i++) if(!dfn[i]) scc(i); cout<<Bcnt<<endl; for(int i=1;i<=n;i++){ if(!B[i].size())continue; for(int j=0;j<B[i].size();j++) cout<<B[i][j]<<' '; puts(""); } }