1. 程式人生 > >Tarjan 強連通分量 + 解釋

Tarjan 強連通分量 + 解釋

日常補東西 這次是Tarjan演算法 原理還是別翻我這裡了......但其他的肯定很詳細~~

強連通分量

先給個題目 見下

題目大意:n (2 ≤ n ≤ 10000) 個點,m (2 ≤ m ≤ 50000) 條邊 求大於1的強連通分量的個數好了Tarjan演算法走起

關於遍歷

Tarjan 演算法最主要的還是兩個陣列

dfn 用來表示當前點的dfs序編號 (同樹剖那個id)

low 則是表示當前點所在強連通分量的 ' 根 ' 就是該強連通分量的點中編號最小的那個 主要用途就是當 dfn[p] == low[p] 時說明該強連通分量搜完了 然後存下來

還有一個數組 (我個人叫他from) 用來存某點所在的強連通分量的編號 但本題用不到 這個更新...放在彈出棧的迴圈裡

遍歷的話這裡用棧(stick然而我嫌名字太長用que代替) 每搜到一個點就丟進去

如何遍歷呢

先把最重要的 dfn 和 low 的 值賦了 一開始都等於遍歷的編號一個點的 dfn 值 等於其 low 值 的時候 說明這是一個強連通分量的根 開始的時候是一樣的 就是把每個點都當成一個強連通分量 之後再去搜索 等到搜完一圈了 此時的點就可以更新到之前某處的點 回溯之後該點的上一個點 上兩個點 上三個點......就都能連到一塊了 這樣一個強連通分量就出來了

但所有的點都要搜到哇 沒搜到的點的話還要繼續哇

於是用一個日常的 o 陣列 判斷是否找過 沒找過就從那個點開始 tarjan 即可

遍歷完了怎麼辦? 開始彈出該強連通分量的所有點 當然別忘了彈出根 建議用 do{} while() 然而我是用 while() 外面再加一次的

然後啥都沒了

之後根據題目所需自己瞎搞搞就好 本題程式碼放下來 連結這裡再放一個

#include <cstdio>
using namespace std;
const int MAXN = 10010;
const int MAXM = 100010;
struct edge {
	int to,next;
} e[MAXM];
int first[MAXN],dfn[MAXN],low[MAXN],que[MAXN],num[MAXN]/*,id[MAXN]*/;
int g,tot,t;
short o[MAXN];
int min(int x,int y) {return x < y ? x : y;}
void add(int x,int y)
{
	e[++tot].next = first[x];
	e[tot].to = y;
	first[x] = tot;
}
void tarjan(int p)
{
	dfn[p] = ++tot;
	low[p] = tot;
	que[++t] = p;
	o[p] = 1;
	int b;
	for (int a = first[p] ; a ; a = e[a].next)
	{
		b = e[a].to;
		if (!dfn[b]) tarjan(b),low[p] = min(low[p],low[b]); else
		if (o[b]) low[p] = min(low[p],dfn[b]);
	}
	if (dfn[p] == low[p])
	{
		++g;
		while (que[t] != p) /*id[que[t]] = p,*/
		++num[g],o[que[t--]] = 0;
		++num[g],o[que[t--]] = 0;
	}
}
int main()
{
	int n,m,x,y;
	scanf("%d%d",&n,&m); while (m--)
	scanf("%d%d",&x,&y),add(x,y);
	tot = 0; for (int a = 1 ; a <= n ; ++ a) if (!o[a]) tarjan(a);
	tot = 0; for (int a = 1 ; a <= g ; ++ a) if (num[a] > 1) ++tot;
	printf("%d\n",tot);
	return 0;
}