割點割邊強連通份量

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("");
	}
}
相關文章
相關標籤/搜索