割點割邊強連通分量
Tarjan 是個著名的電腦科學家,他發明了很多演算法,在求解圖的連通性有關問題時,最著名的應該是割點割邊和強連通分量。
什麼是割點和割邊
在圖中去掉這個點和它的所有直接連邊,原來聯通的圖就不聯通了,那它就是割點。
在圖中去掉這條邊,原來聯通的圖就不聯通了,那它就是割邊。
非連通圖的所有連通塊的割點(割邊)集合的並是它的割點(割邊)集合。
怎麼找割點割邊
tarjan 演算法核心在於 dfs,將圖轉化為 dfs 樹,並記錄 dfn(一種dfs序,又名時間戳)。
考慮對圖 dfs,dfn[i] 代表 i 節點的訪問次序。一次 dfs 下來,由於我們有 if(vis[y])continue;
的判斷,所以必然有一些邊我們沒有走,那那些我們走過的邊就必然構成一棵 dfs 樹,那些捨棄的邊就是 dfs 樹中的反向邊。
首先,考慮樹根。只要樹根有多於1個兒子,它就是割點
令 low[i] 表示節點 i 的子樹中的節點通過各自的反向邊(只走反向邊)能夠向上回溯到的dfn最小的節點。
想想,\(low[y]\ge dfn[x]\) (y是x的兒子之一)是什麼意思?就說明y子樹中的節點沒有跟x子樹外部節點的有效連邊。因此,x 就是一個割點。
割邊也很好找,因為反向邊必然不是割邊,因此只要 dfn[y]>x 那邊\(x\leftrightarrow y\) 就是割邊。且,這時不用特判樹根
程式碼演示(割點)
#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("");
}
}