Tarjan

1.求解強連通份量

1.1定義:

有向圖強連通份量:在有向圖G中,若是兩個頂點vi,vj間(vi>vj)有一條從vi到vj的有向路徑,同時還有一條從vj到vi的有向路徑,則稱兩個頂點強連通(strongly connected)。若是有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。有向圖的極大強連通子圖,稱爲強連通份量。ios

1.2求法

經過肉眼能夠很直觀地看出1-2-3是一組強連通份量,但很遺憾,機器並無眼睛,因此該怎麼判斷強連通份量呢?算法

img

上面那張圖,咱們對它進行dfs遍歷。能夠注意到紅邊很是特別,由於若是按照遍歷時間來分類的話,其餘邊都指向在本身以後被遍歷到的點,而紅邊指向的則是比本身先被遍歷到的點。數組

從一個點出發,一直向下遍歷,而後忽得找到一個點,那個點居然有條指回這一個點的邊!post

那麼想必這個點可以從自身出發再回到自身spa

想必這個點和其餘向下遍歷的該路徑上的全部點構成了一個環,code

想必這個環上的全部點都是強聯通的。blog

可是咱們想要知道的是這個圖的強聯通份量。get

只須要知道這個點u下面的全部子節點有沒有連着u的祖先就好了。string

但是咱們怎麼知道這個點u它下面的全部子節點必定是都與他強聯通的呢?it

這彷佛是不對的,這個點u之下的全部點不必定都強聯通

那麼怎麼在退回到這個點的時候,知道全部和這個點u構成強連通份量的點呢?

開個棧記錄就好了

具體實現還要看代碼。

1.3代碼

咱們須要幾個數組來完成Tarjan算法

  1. dfn 數組表示該結點是第幾個遍歷到的
  2. low數組表示該結點,及其它的子結點,可以到達的全部結點中dfn最小的點,也就是最先遍歷到的點
  3. stack 即上文所說的棧,表示當前全部可能存在於當前強聯通份量點的集合。即若是這裏面的某些點已經組成了強連通份量,就把這全部的點彈出。
  4. vis 數組表示這個點會否在棧裏

接下來咱們開始遍歷每個點。

遍歷到u時,首先初始化u的dfn和low都爲當前節點的遍歷序號。

而後將u打入棧中,去遍歷它的每一個子節點,若是它的子節點沒有被訪問到,就訪問,若是訪問到了,就不去訪問。u全部子節點的最小low值,即爲u的low值

在u及其子節點訪問完後,它的low值不再會變化。

若是作完了上面這一切,咱們如何判斷u及其一些子節點可否變成一個強聯通份量。

注意:這裏,若是u的子節點已經本身成爲了一個強聯通份量,它已經出棧

試想一下,若是這時候u的dfn和low值相同,會怎麼樣?

兩種狀況:

  1. u的子節點(還在棧中的)所有能回到u,且回不到比u更早遍歷的節點
  2. u的全部子節點都回不到u,這時u本身一個節點是一個強聯通份量。

若是是第1種狀況,那麼比u後遍歷到的節點,在棧中的,必定能夠和u構成一個強連通份量。

若是是第二種狀況,表明着u的全部子節點都沒法和u構成強連通份量,也就不在棧中。

因此,若是出現這種狀況(u的dfn和low值相同),那麼在棧中的u上面的節點(不是都說棧頂嗎,咱們假設這個棧是站立的)和u必定能構成強連通份量。

不然,u的low值必定比dfn值小,這時必定會存在一個u的祖先節點,或是另外一分支的節點。

例題:https://www.luogu.com.cn/problem/P3387

代碼:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ll long long
#define ld long double
#define ull unsigned long long
#define N 20000
#define M 100100
using namespace std;

inline int Max(int a,int b){
	return a>b?a:b;
}

inline int Min(int a,int b){
	return a>b?b:a;
}

struct edge{
	int to,next;
	inline void intt(int to_,int next_){
		to=to_;next=next_;
	}
};
edge li[M],li2[M];
int head[N],tail,head2[N],tail2;

inline void add(int from,int to){
	li[++tail].intt(to,head[from]);
	head[from]=tail;
}

inline void add2(int from,int to){
	li2[++tail2].intt(to,head2[from]);
	head2[from]=tail2;
}

int dfn[N],low[N],stack[N],top,deep,co_num,co[N];
bool vis[N];

inline void tarjan(int u){
	dfn[u]=++deep;low[u]=deep;stack[++top]=u;vis[u]=1;
	for(int x=head[u];x;x=li[x].next){
		int to=li[x].to;
		if(!dfn[to]){
			tarjan(to);
			low[u]=Min(low[u],low[to]);
		}
		else if(vis[to]) low[u]=Min(low[u],low[to]);
	}
	if(low[u]==dfn[u]){
		co[u]=++co_num;
		vis[u]=0;
		while(stack[top]!=u){
			co[stack[top]]=co_num;
			vis[stack[top]]=0;
			top--;
		}
		top--;
	}
}

int n,m,a[N],p[N],f[N];

inline void dfs1(int u){
	vis[u]=1;
	for(int x=head[u];x;x=li[x].next){
		int to=li[x].to;
		if(co[to]!=co[u]) add2(co[u],co[to]);
		if(!vis[to]) dfs1(to);
	}
}

inline int dfs2(int u){
	
	if(f[u]) return f[u];
	for(int x=head2[u];x;x=li2[x].next){
		int to=li2[x].to;
		f[u]=Max(f[u],dfs2(to));
	}
	f[u]+=p[u];
	return f[u];
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=m;i++){
		int from,to;
		scanf("%d%d",&from,&to);
		add(from,to);
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++) if(!vis[i]) dfs1(i);
	for(int i=1;i<=n;i++) p[co[i]]+=a[i];
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=co_num;i++) if(!f[i]) f[i]=dfs2(i);
	int maxx=-1;
	for(int i=1;i<=co_num;i++) maxx=Max(maxx,f[i]);
	printf("%d",maxx);
	return 0;
}

這個題博主作麻煩了。實際上能夠在原來的路徑上直接改圖,可是出題人沒有像z*c同樣愛卡空間和時間。因此能過。

2.Tarjan求割點

tarjan算法仍是能夠求割點的。

在一個無向圖中,若是有一個頂點集合,刪除這個頂點集合以及這個集合中全部頂點相關聯的邊之後,圖的連通份量增多,就稱這個點集爲割點集合

簡單點說就是你把割點去掉後,這個圖就好似被撕成了幾個部分同樣,各個部分之間再也不聯通。

仍是同樣,咱們繼續用上面的dfn和low數組,可是這裏,low數組的定義會有變化

定義以下:最先能繞到的點

由於是無向圖,因此不存在祖先。

同時,若是比u晚遍歷到的點的low值還比u的dfn小的話(或者等於),那麼去掉點u,確定會多一個聯通圖。

同時還要注意判斷dfs樹的根節點,若是根節點兒子數目大於等於2,則根節點也是一個割點。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ll long long
#define ld long double
#define ull unsigned long long
#define N 30010
#define M 200100
using namespace std;

struct edge{
	int to,next;
	inline void intt(int to_,int next_){
		to=to_;next=next_;
	}
};
edge li[M];
int head[N],tail;

inline int Min(int a,int b){
	return a>b?b:a;
}

inline void add(int from,int to){
	li[++tail].intt(to,head[from]);
	head[from]=tail;
}

int dfn[N],low[N],deep;
bool iscut[N];

inline void tarjan(int u,int fa){
	dfn[u]=++deep;low[u]=deep;
	int child=0;
	for(int x=head[u];x;x=li[x].next){
		int to=li[x].to;
		if(!dfn[to]){
			child++;
			tarjan(to,u);
			low[u]=Min(low[u],low[to]);
			if(low[to]>=dfn[u]) iscut[u]=1;
		}
		else low[u]=Min(low[u],dfn[to]);//尤爲注意這句話
	}
	if(fa==-1&&child==1) iscut[u]=0;
}

int n,m;

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int from,to;
		scanf("%d%d",&from,&to);
		add(from,to);
		add(to,from);
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,-1);
	int ans=0;
	for(int i=1;i<=n;i++) if(iscut[i]) ans++;
	printf("%d\n",ans);
	for(int i=1;i<=n;i++) if(iscut[i]) printf("%d ",i);
}

引用https://www.luogu.com.cn/blog/styx-ferryman/chu-tan-tarjan-suan-fa-qiu-qiang-lian-tong-fen-liang-post

相關文章
相關標籤/搜索