強聯通分量tarjan

YY'cwj發表於2020-11-26

一.模板介紹

模板題

1.定義

給定一張有向圖,若存在一個點集,該點集內的點可通過路徑任意到達其他所有點,則稱該點集為一個強聯通分量。(一個點也可為一個強聯通分量)

2.演算法tarjan

結果:將強聯通分量縮成一個點,讓一個有環圖變成一張無環圖

#include<bits/stdc++.h>
using namespace std;
const int N=20005;
const int M=1e5+5;
struct ppp {
	int u,v,next;
} e[2*M];
int vex[N],k;
int dfn[N],low[N],cnt;
int stk[N],top,color[N],co,ans[N],instk[N];
int n,m;
//dfn為次序,low為該點所能遍歷的次序最早的點 
void add(int u,int v) {
	k++;
	e[k].u=u;
	e[k].v=v;
	e[k].next=vex[u];
	vex[u]=k;
	return;
}
void tarjan(int u) {
	dfn[u]=low[u]=++cnt;
	instk[u]=1;
	stk[++top]=u;//用棧儲存強連通,u點入棧 
	for(int i=vex[u]; i; i=e[i].next) {
		int v=e[i].v;
		if(!dfn[v]) {
			tarjan(v,root);
			low[u]=min(low[v],low[u]);
			//如果v能遍歷到的點比u能遍歷到的最小點小,就改值 
		}
		else if(instk[v])low[u]=min(low[u],dfn[v]);
		//如果v的次序比u的遍歷到的最小點小,就改值 
	}
	if(dfn[u]==low[u]){//則該點為強連通的最早點 
		co++;
		while(1){
			int t=stk[top];//從棧頂到這個點的所有點,都是該強聯通分量的點集 
			color[t]=co;//屬於一個強聯通分量的點顏色相同 
			top--;
			instk[t]=0;//出棧 
			if(t==u)break;
		}
	} 
}
void solve(){
	co=n; //如果需要對縮完後的點建圖,則co要從n+1開始
	for(int i=1; i<=n; i++) {
       if(!dfn[i])tarjan(i,i);
	}
	for(int u=1;u<=n;u++){
		for(int i=vex[u];i;i=e[i].next){
			int v=e[i].v;
			if(color[u]!=color[v])add(color[u],color[v]);
			//後來是以縮點後的強聯通分量顏色來建無向圖
		}
	}
}
int main() {
	int u,v;
	cin>>n>>m;
	for(int i=1; i<=m; i++) {
		cin>>u>>v;
		add(u,v);
		add(v,u);
	}
	solve(); 
	return 0;
}  

3.開始刷題

例題1:訊息擴散

只需要在tarjan完之後,求入度為0的點的數量即可

void xxks(){
	int ans=0;
	for(int u=1;u<=n;u++){
		for(int i=vex[u];i;i=e[i].next){
			int v=e[i].v;
			if(color[u]!=color[v])in[color[v]]++;
			//求入度為0的點為擴散源 
		}
	}
	for(int i=1;i<=co;i++){
		if(in[i]==0)ans++;
	}
	cout<<ans;
} 

二.割點

割點模板題

1.定義

給定一張無向連通圖,若存在點u滿足刪除該點後,連通圖不再連通,則該點u為該圖的割點

2.演算法tarjan

過程

tarjan跑一個深搜優先樹
對於根節點:若其有兩個以上的孩子,則該點為割點
對於非根點:若從u出發跑,不能到達u的上層,則u點為割點

根據定義很好寫出程式碼

void tarjan(int u,int root) {
	dfn[u]=low[u]=++cnt;
	instk[u]=1;
	stk[++top]=u;
	int child=0;
	for(int i=vex[u]; i; i=e[i].next) {
		int v=e[i].v;
		if(!dfn[v]) {
			tarjan(v,root);
			low[u]=min(low[v],low[u]);
			if(low[v]>=dfn[u]&&u!=root)cut[u]=1;
            //由子節點出發能到的最早點在該節點後面或等於該節點則為割點
			//不為if(low[u]>=dfn[u])的原因:只要由一個子節點滿足情況則u就為割點 
			if(u==root)child++;//統計根節點的孩子數 
		}
		low[u]=min(low[u],dfn[v]);
	}
	if(u==root&&child>=2)cut[u]=1;//根節點有兩個子樹則為割點 
	if(dfn[u]==low[u]){
		co++;
		while(1){
			int t=stk[top];
			color[t]=co;
			top--;
			instk[t]=0;
			if(t==u)break;
		}
	} 
}

相關文章