淺談Tarjan算法

預備知識

  設無向圖$G_{0} = (V_{0}, E_{0})$,其中$V_{0}$爲定點集合,$E_{0}$爲邊集,設有向圖$G_{1} = (V_{1}, E_{1})$,其中$V_{1}$爲定點集合,$E_{1}$爲邊集。node

  • 無向圖中的路徑:若是存在一個頂點序列$v_{p},v_{i_{1}},\cdots,v_{i_{k}},v_{q}$,使得$\left ( v_{p}, v_{i_{1}} \right ),\left ( v_{i_{1}},v_{i_{2}} \right ),\cdots,\left ( v_{k-1}, v_{k} \right ),\left ( v_{k}, v_{q} \right )\in E_{0}$,那麼稱$v_{p}$到$v_{q}$之間存在一條路徑。
  • 有向圖中的路徑:若是存在一個頂點序列$v_{p},v_{i_{1}},\cdots,v_{i_{k}},v_{q}$,使得$\left ( v_{p}, v_{i_{1}} \right ),\left ( v_{i_{1}},v_{i_{2}} \right ),\cdots,\left ( v_{k-1}, v_{k} \right ),\left ( v_{k}, v_{q} \right )\in E_{1}$,那麼稱$v_{p}$到$v_{q}$之間存在一條路徑。
  • 連通圖:在無向圖中,對於任意有序對$\left ( v_{i},v_{j} \right )\in(V_{0}\times V_{0})$,都存在一條$v_{i}$到$v_{j}$的路徑。
  • 強連通圖:在有向圖中,對於任意有序對$\left ( v_{i},v_{j} \right )\in(V_{1}\times V_{1})$,都存在一條$v_{i}$到$v_{j}$的路徑。
  • 連通份量:無向圖$G$的極大連通子圖是$G$的連通份量。
  • 強連通份量:有向圖$G$的極大強連通子圖是$G$的強連通份量。
  • 割點:在無向圖$G$中,若是存在點$v_{0}$知足刪除它後,新圖的連通份量的個數增長,那麼$v_{0}$爲原圖的一個割點。
  • 割邊(又稱爲):在無向圖$G$中,若是存在邊$e_{0}$知足刪除它後,新圖的連通份量的個數增長,那麼$e_{0}$爲原圖的一條割邊。
  • 邊-雙連通圖:若無向連通圖$G$中不存在割邊,則無向圖$G$是邊-雙連通圖
  • 點-雙連通圖:若無向連通圖$G$中不存在割點,則無向圖$G$是點-雙連通圖
  • 點-雙連通份量:無向圖$G$中的極大點-雙連通子圖是它的點-雙連通份量
  • 邊-雙連通份量:無向圖$G$中的極大邊-雙連通子圖是它的邊-雙連通份量

兩個數組

  $dfn_i$:點$i$的深度優先數(英文多是Depth First Number),能夠理解爲是第幾個被搜索到的節點。ios

  $low_i$:在點$i$的dfs子樹中經過1條返祖邊到達的最先祖先。c++

  Tarjan算法首先會對原圖進行深度優先搜索。算法

  當從一個訪問過的點經過邊$e$到達一個未訪問的點,則將邊$e$標記爲樹邊。若是一條非樹邊$(u, v)$使得要麼$u$是$v$的祖先知足,要麼$v$是$u$的祖先,那麼稱邊$(u, v)$是一條返祖邊數組

  顯然當遇到一條返祖邊時,須要用它來更新當前點的$low$值app

  經過Tarjan算法獲得的生成森林是dfs生成森林:ide

  (其中虛邊是返祖邊)spa

Tarjan算法的應用

求割點和割邊

  首先給出一個結論code

定理1 無向圖中的每一條邊,要麼是樹邊,要麼是返祖邊。component

  證實 假如存在其餘邊,它知足它不連它的祖先也不連它的後代,那麼它必定是知足:

  而後根據dfs的性質和無向邊的性質,容易獲得不存在這種狀況。

  假如如今考慮點$p$是否是割點,分兩種狀況討論:

  • 若是$p$是樹根,那麼只要$p$的子節點個數大於1,那麼$p$就是割點。由於$p$不存在祖先,點$p$的不一樣子樹中的兩點之間的路徑必須通過點1(由於根節點的位於它不一樣子樹內的兩點的LCA是根節點,而後根據定理1獲得,從其中一個點開始走,通過的每一條邊要麼到它的後代要麼到它的祖先,因此一定通過1)。
  • 若是$p$不是樹根,那麼只要存在一個子節點$x$知足$low_x \geqslant dfn_p$,那麼點$p$是割點。由於假如點$p$被刪掉後,點$x$沒法到達$p$的祖先,而這以前是能夠到達的,因此連通份量的個數至少增長了1.

Code

 1 /**
 2  * poj
 3  * Problem#1144
 4  * Accepted
 5  * Time: 16ms
 6  * Memory: 672k
 7  */
 8 #include <algorithm>
 9 #include <iostream>
10 #include <cstring>
11 #include <cstdio>
12 #include <vector>
13 using namespace std;
14 typedef bool boolean;
15 
16 const int N = 105;
17 
18 int n;
19 int cnt, res;
20 int dfn[N], low[N];
21 boolean vis[N];
22 vector<int> g[N];
23 
24 inline boolean init() {
25     scanf("%d", &n);
26     if (!n)    return false;
27     for (int i = 1; i <= n; i++)
28         g[i].clear();
29     int u, v;
30     while (~scanf("%d", &u) && u) {
31         while (getchar() != '\n') {
32             scanf("%d", &v);
33             g[u].push_back(v);
34             g[v].push_back(u);
35         }
36     }
37     return true;
38 }
39 
40 void tarjan(int p, int fa) {
41     int cson = 0;
42     dfn[p] = low[p] = ++cnt;
43     vis[p] = true;
44     for (int i = 0; i < (signed)g[p].size(); i++) {
45         int e = g[p][i];
46         if (e == fa)    continue;
47         if (!vis[e]) {
48             tarjan(e, p);
49             low[p] = min(low[p], low[e]);
50             if (low[e] >= dfn[p])
51                 cson++;
52         } else
53             low[p] = min(low[p], dfn[e]);
54     }
55     if ((!fa && cson > 1) || (fa && cson))
56         res++;
57 }
58 
59 inline void solve() {
60     cnt = 0, res = 0;
61     memset(vis, false, sizeof(boolean) * (n + 1));
62     for (int i = 1; i <= n; i++)
63         if (!vis[i])
64             tarjan(i, 0);
65     printf("%d\n", res);
66 }
67 
68 int main() {
69     while (init()) {
70         solve();
71     }
72     return 0;
73 }
Cut Vectex

  求橋的話,相對就簡單一些。

  一個很是顯然的結論:

定理2 返祖邊不多是橋。

  證實 由於返祖邊的兩端必定經過樹邊連通。因此刪掉返祖邊不會改變圖的連通性。

  所以割邊的個數不會超過$n - 1$(一個顯然,但沒多大用的性質)。

  我更想說的是,再根據定理1能夠獲得橋必定是樹邊

  考慮什麼樣的樹邊被斷掉後圖的連通份量的個數增長。假如這條樹邊兩端的點是$u, v$,其中$u$是$v$的爸爸父節點。刪掉邊$(u, v)$後,連通份量個數增長的充分必要條件是$v$沒法經過返祖邊到達$u$或者$u$的祖先。所以,不可貴到條件是$low_v = dfn_v$。

Code

 1 /**
 2  * hdu
 3  * Problem#4738
 4  * Accepted
 5  * Time: 187ms
 6  * Memory: 25264k
 7  */ 
 8 #include <iostream>
 9 #include <cstring>
10 #include <cstdio>
11 using namespace std;
12 typedef bool boolean;
13 
14 const int N = 1005, M = 2e6 + 5;
15 
16 typedef class Edge {
17     public:
18         int end;
19         int next;
20         int w;
21 
22         Edge(int end = 0, int next = 0, int w = 0):end(end), next(next), w(w) {    }
23 }Edge;
24 
25 typedef class MapManager {
26     public:
27         int ce;
28         int h[N];
29         Edge es[M];
30 
31         void addEdge(int u, int v, int w) {
32             es[++ce] = Edge(v, h[u], w);
33             h[u] = ce;
34         }
35 
36         void addDoubleEdge(int u, int v, int w) {
37             addEdge(u, v, w);
38             addEdge(v, u, w);
39         }
40 
41         Edge& operator [] (int p) {
42             return es[p];
43         }
44 }MapManager;
45 
46 int n, m;
47 int cnt, res;
48 int dfn[N], low[N];
49 boolean vis[N];
50 MapManager g;
51 
52 inline boolean init() {
53     scanf("%d%d", &n, &m);
54     if (!n && !m)
55         return false;
56     g.ce = -1;
57     memset(g.h, -1, sizeof(int) * (n + 1));
58     for (int i = 1, u, v, w; i <= m; i++) {
59         scanf("%d%d%d", &u, &v, &w);
60         g.addDoubleEdge(u, v, w);
61     }
62     return true;
63 }
64 
65 void tarjan(int p, int laste) {
66     dfn[p] = low[p] = ++cnt;
67     vis[p] = true;
68     for (int i = g.h[p]; ~i; i = g[i].next) {
69         int e = g[i].end, w = g[i].w;
70         if (i == laste)    continue;
71         if (!vis[e]) {
72             tarjan(e, i ^ 1);
73             low[p] = min(low[p], low[e]);
74             if (low[e] == dfn[e])
75                 res = min(res, w);
76         } else
77             low[p] = min(low[p], dfn[e]);
78     }
79 }
80 
81 inline void solve() {
82     cnt = 0, res = 211985;
83     memset(vis, false, sizeof(boolean) * (n + 1));
84     tarjan(1, -1);
85     if (!res) res = 1;            // 坑.... 
86     for (int i = 2; i <= n; i++)
87         if (!vis[i]) {
88             res = 0; 
89             break;
90         }
91     if (res == 211985)    res = -1;
92     printf("%d\n", res);
93 }
94 
95 int main() {
96     while (init())
97         solve();
98     return 0;
99 }
Cut Edge

求點-雙連通份量

   在求點-雙連通份量(如下簡稱爲點雙)以前,咱們再來證實一個東西:

定理3 每條邊剛好屬於一個點雙。

  證實 首先來講明每條邊必定屬於一個點雙連通子圖。考慮這條邊的兩個端點以及它自己構成的子圖,顯然它是點雙連通的。

  而後來講明任意兩個點雙沒有公共邊。

  假設存在兩個點雙有一條公共邊$(u, v)$,假設它們的點集分別爲$V_1, V_2$。若是刪掉的點$x\in V_1$或$x\in V_2$,且$x\neq u, x\neq v$,根據點雙的定義容易獲得新圖的連通性不會改變。若是刪掉的點是$u$,那麼剩餘的點必定與$v$連通,因此它仍然不會改變圖的連通性。對於若是刪掉的點是$v$同理可證不會改變新圖的連通性。因此刪掉$V_1 \cup V_2$中任意一個點都不會改變圖的連通性。所以它們可以組成更大的一個點雙連通子圖,與點雙的定義矛盾。

  由這個證實過程不可貴到點雙的點集大小至少爲2,因此在考慮找到全部點雙的時候能夠考慮邊。

定理4 每一個點雙包含至少一條樹邊。

   證實 假設存在一個點雙不包含任意一條樹邊。咱們考慮從這個點雙中選取2個不一樣點$u, v$,它們在dfs生成樹上存在惟一一條路徑。咱們把它加入這個點雙中,若是刪掉非路徑上的點,那麼剩餘點與$u, v$連通。若是刪掉的是$u$或者$v$,那麼剩下點會與另一個點連通。若是刪掉的是路徑上的一個點(不含端點),那麼路徑上一部分點會與$u$連通,另外一部分與$v$,$u, v$和原點雙中的點必定連通。因此新子圖仍是一個點雙連通子圖,與點雙的定義矛盾。

  因此點雙的數量不會超過$n - 1$。

  咱們考慮在回溯的時候找到一個點雙。

  考慮判斷一條樹邊是否是一個點雙內的樹邊中深度最低的一條邊。假設它深度較深的一端是$v$,較淺的一端是$u$。那麼當$v$沒法連向$u$的祖先的時候,加入$u$後再加入另外一條與$u$連通的樹邊,那麼刪掉$u$後就會多產生連通塊,因此當$low_v \geqslant dfn_u$的時候,這條邊是這個點雙內的最後一條邊。

  若是須要求出點雙內的全部點和全部邊能夠用一個棧記錄一下邊。

  注意一下返祖邊只在子節點的時候加入,這個能夠經過判斷深度優先數的大小解決掉(你必定也不但願在其餘某個點雙內莫名其妙多出一條邊)。

Code

  1 /**
  2  * poj
  3  * Problem#2942
  4  * Accepted
  5  * Time: 1063ms
  6  * Memory: 1128k
  7  */
  8 #include <iostream>
  9 #include <cstdlib>
 10 #include <cstdio>
 11 #include <vector>
 12 #include <stack>
 13 using namespace std;
 14 typedef bool boolean;
 15 
 16 template <typename T>
 17 void pfill(T* pst, const T* ped, T val) {
 18     for ( ; pst != ped; *(pst++) = val);
 19 }
 20 
 21 const int N = 1e3 + 3, M = (N * N) << 1;
 22 
 23 typedef class Edge {
 24     public:
 25         int ed, nx;
 26 
 27         Edge(int ed = 0, int nx = 0):ed(ed), nx(nx)    {    }
 28 }Edge;
 29 
 30 typedef class MapManager {
 31     public:
 32         int h[N];
 33         vector<Edge> es;
 34 
 35         void addEdge(int u, int v) {
 36             es.push_back(Edge(v, h[u]));
 37             h[u] = (signed) es.size() - 1;
 38         }
 39 
 40         Edge& operator [] (int p) {
 41             return es[p];
 42         }
 43 }MapManager;
 44 
 45 #define pii pair<int, int>
 46 
 47 int n, m;
 48 int col[N];
 49 stack<pii> s;
 50 MapManager g;
 51 int dfs_clock;
 52 boolean res[N];
 53 MapManager subg;
 54 boolean rg[N][N];
 55 int dfn[N], low[N];
 56 vector<int> bpoints;
 57 
 58 inline boolean init() {
 59     scanf("%d%d", &n, &m);
 60     if (!n && !m)
 61         return false;
 62     g.es.clear();
 63     dfs_clock = 0;
 64     pfill(dfn, dfn + n + 1, 0);
 65     pfill(col, col + n + 1, -1);
 66     pfill(g.h, g.h + n + 1, -1);
 67     pfill(res, res + n + 1, false);
 68     pfill(subg.h, subg.h + n + 1, -1);
 69     for (int i = 1; i <= n; i++)
 70         for (int j = 1; j <= n; j++)
 71             rg[i][j] = false;
 72     for (int i = 1, u, v; i <= m; i++) {
 73         scanf("%d%d", &u, &v);
 74         rg[u][v] = rg[v][u] = true;
 75     }
 76     return true;
 77 }
 78 
 79 boolean color(int p, int c) {
 80     if (~col[p])
 81         return col[p] == c;
 82     col[p] = c;
 83     for (int i = subg.h[p]; ~i; i = subg[i].nx)
 84         if (!color(subg[i].ed, col[p] ^ 1))
 85             return false;
 86     return true;
 87 }
 88 
 89 void dispose() {
 90     if (!color(bpoints[0], 0)) {
 91         for (unsigned i = 0; i < bpoints.size(); i++)
 92             res[bpoints[i]] = true;
 93     }
 94     for (unsigned i = 0; i < bpoints.size(); i++)
 95         subg.h[bpoints[i]] = -1, col[bpoints[i]] = -1;
 96     subg.es.clear();
 97     bpoints.clear();
 98 }
 99 
100 void tarjan(int p, int last_edge) {
101     dfn[p] = low[p] = ++dfs_clock;
102     
103     pii now, cur;
104     for (int i = g.h[p], e; ~i; i = g[i].nx) {
105         e = g[i].ed;
106         if (i == (last_edge ^ 1))
107             continue;
108         now = pii(min(p, e), max(p, e));
109         if (!dfn[e]) {
110             s.push(now);
111             tarjan(e, i);
112             low[p] = min(low[p], low[e]);
113             if (low[e] >= dfn[p]) {
114                 do {
115                     cur = s.top();
116                     s.pop();
117                     subg.addEdge(cur.first, cur.second);
118                     subg.addEdge(cur.second, cur.first);
119                     bpoints.push_back(cur.first);
120                     bpoints.push_back(cur.second);
121                 } while (now != cur);
122                 dispose();
123             }
124         } else {
125             low[p] = min(low[p], dfn[e]);
126             if (dfn[e] < dfn[p])
127                 s.push(pii(min(p, e), max(p, e)));
128         }
129     }
130 }
131 
132 inline void solve() {
133     for (int i = 1; i <= n; i++)
134         for (int j = i + 1; j <= n; j++)
135             if (!rg[i][j]) {
136                 g.addEdge(i, j);
137                 g.addEdge(j, i);
138             }
139     
140     for (int i = 1; i <= n; i++)
141         if (!dfn[i])
142             tarjan(i, -1);
143     
144     int answer = 0;
145     for (int i = 1; i <= n; i++)
146         answer += !res[i];
147     printf("%d\n", answer);
148 }
149 
150 int main() {
151     while (init())
152         solve();
153     return 0;
154 }
Vertex Biconnected Component

  有時候咱們但願求出點雙內的點,這個時候棧內記錄邊略顯得麻煩,能夠考慮找到一個點的時候加入一個點,它在找到包含它到它的父節點的那條樹邊的點雙時被彈出棧。具體細節能夠見代碼。

Code

/**
 * loj
 * Problem#2562
 * Accepted
 * Time: 2431ms
 * Memory: 21964k
 */
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

const int N = 1e5 + 5, N2 = N << 1;

template <typename T>
void pfill(T* pst, const T* ped, T val) {
	for ( ; pst != ped; *(pst++) = val);
}

typedef class Edge {
	public:
		int ed, nx;

		Edge() {	}
		Edge(int ed, int nx) : ed(ed), nx(nx) {	}
} Edge;

typedef class MapManager {
	public:
		int h[N << 1];
		vector<Edge> es;

		void init(int n) {
			pfill(h + 1, h + n + 1, -1);
			es.clear();
		}
		
		void add_edge(int u, int v) {
			es.emplace_back(v, h[u]);
			h[u] = (signed) es.size() - 1;
		}
		Edge& operator [] (int p) {
			return es[p];
		}
} MapManager;

int Case;
int n, m;
MapManager G, Tr;

int cnt_node, dfs_clock;
int value[N2];

inline void init() {
	scanf("%d%d", &n, &m);
	G.init(n);
	Tr.init(n << 1);
	cnt_node = n;
	pfill(value + 1, value + n + 1, 1);
	for (int i = 1, u, v; i <= m; i++) {
		scanf("%d%d", &u, &v);
		G.add_edge(u, v);
		G.add_edge(v, u);
	}
}

stack<int> S;
boolean vis[N];
int dfn[N], low[N];

void init_tarjan() {
	dfs_clock = 0;
	while (!S.empty()) S.pop();
	pfill(vis + 1, vis + n + 1, false);
}
void Tarjan(int p) {
	S.push(p);
	vis[p] = true;
	dfn[p] = low[p] = ++dfs_clock;
	for (int i = G.h[p], e; ~i; i = G[i].nx) {
		e = G[i].ed;
		if (!vis[e]) {
			Tarjan(e);
			low[p] = min(low[p], low[e]);
			if (low[e] >= dfn[p]) {
				int now = -1, id = ++cnt_node;
				value[id] = 0;
				do {
					now = S.top();
					S.pop();
					Tr.add_edge(id, now);
					Tr.add_edge(now, id);
				} while (now != e);
				Tr.add_edge(id, p);
				Tr.add_edge(p, id);
			}
		} else {
			low[p] = min(low[p], dfn[e]);
		}
	}
}

int sz[N2], zson[N2], dep[N2];
int in[N2], top[N2], fa[N2];

void dfs1(int p, int Fa) {
	int mx = -1, &id = zson[p];
	sz[p] = 1, fa[p] = Fa;
	value[p] += value[Fa], dep[p] = dep[Fa] + 1, id = -1;
	for (int i = Tr.h[p], e; ~i; i = Tr[i].nx) {
		e = Tr[i].ed;
		if (e ^ Fa) {
			dfs1(e, p);
			sz[p] += sz[e];
			if (sz[e] > mx) {
				mx = sz[e];
				id = e;
			}
		}
	}
}

void dfs2(int p, boolean ontop) {
	in[p] = ++dfs_clock;
	top[p] = (!ontop) ? (top[fa[p]]) : (p);
	if (~zson[p]) {
		dfs2(zson[p], false);
	}
	for (int i = Tr.h[p], e; ~i; i = Tr[i].nx) {
		e = Tr[i].ed;
		if (e != fa[p] && e != zson[p]) {
			dfs2(e, p);
		}
	}
}

int lca(int u, int v) {
	while (top[u] != top[v]) {
		if (dep[top[u]] < dep[top[v]]) {
			swap(u, v);
		}
		u = fa[top[u]];
	}
	return (dep[u] < dep[v]) ? (u) : (v);
}

inline void solve() {
	static int S[N2];
	
	init_tarjan();
	Tarjan(1);

	dfs_clock = 0;
	dfs1(1, 0);
	dfs2(1, true);

	int Q, K;
	scanf("%d", &Q);
	while (Q--) {
		scanf("%d", &K);
		for (int i = 1; i <= K; i++) {
			scanf("%d", S + i);
		}
		
		sort(S + 1, S + K + 1, [&] (const int& u, const int& v) {
			return in[u] < in[v];
		});
		int vd = value[fa[lca(S[1], S[K])]];
		int ans = value[S[1]] - vd, g;
		for (int i = 1; i < K; i++) {
			g = lca(S[i], S[i + 1]);
			ans += value[S[i + 1]] - value[g];
		}
		printf("%d\n", ans - K);
	}
}

int main() {
	scanf("%d", &Case);
	while (Case--) {
		init();
		solve();
	}
	return 0;
}

求邊-雙連通份量

  (下面將邊-雙連通份量簡寫爲邊雙)

  和點雙相似,只不過此次咱們考慮頂點:

定理5 每一個頂點剛好屬於一個邊雙。

  證實 首先一個點的圖是邊雙。

  假設存在兩個邊雙存在一個公共點$x$,那麼刪掉一條邊都不會改變$x$和剩下的點的連通性。

  咱們考慮一個點是否是邊雙中的最淺的一個點。若是$dfn_p = low_p$,那麼再加入它的某個父節點,那麼它將成爲割點。

  若是須要求出邊雙內的全部點再用一個棧記錄一下點就行了。

  彷佛還有一種作法是邊雙內必定不包含原圖的橋,所以咱們找到全部的橋,把它們刪掉就獲得了全部邊雙了。這個條件只是必要性,它的充分性能夠考慮若是它刪掉後使得連通塊個數增長,那麼它必定是原圖的橋。(它是原圖的一個子圖,那麼再加入若干端點都在它內部邊後不會使得它的邊連通性下降)。

Code

  1 /**
  2  * poj
  3  * Problem#3177
  4  * Accepted
  5  * Time: 47ms
  6  * Memory: 744k
  7  */
  8 #include <iostream>
  9 #include <cstdlib>
 10 #include <cstdio>
 11 #include <vector>
 12 #include <stack>
 13 using namespace std;
 14 typedef bool boolean;
 15 
 16 template <typename T>
 17 void pfill(T* pst, const T* ped, T val) {
 18     for ( ; pst != ped; *(pst++) = val);
 19 }
 20 
 21 const int N = 5e3 + 3, M = N << 1;
 22 
 23 typedef class Edge {
 24     public:
 25         int ed, nx;
 26 
 27         Edge(int ed = 0, int nx = 0):ed(ed), nx(nx)    {    }
 28 }Edge;
 29 
 30 typedef class MapManager {
 31     public:
 32         int* h;
 33         vector<Edge> es;
 34 
 35         MapManager() {    }
 36         MapManager(int n) {
 37             h = new int[(n + 1)];
 38             pfill(h + 1, h + n + 1, -1);
 39         }
 40 
 41         void addEdge(int u, int v) {
 42             es.push_back(Edge(v, h[u]));
 43             h[u] = (signed) es.size() - 1;
 44         }
 45 
 46         Edge& operator [] (int p) {
 47             return es[p];
 48         }
 49 }MapManager;
 50 
 51 int n, m;
 52 int deg[N];
 53 MapManager g;
 54 stack<int> s;
 55 int dfs_clock;
 56 int dfn[N], low[N];
 57 pair<int, int> es[M];
 58 
 59 inline void init() {
 60     scanf("%d%d", &n, &m);
 61     g = MapManager(n);
 62     for (int i = 1, u, v; i <= m; i++) {
 63         scanf("%d%d", &u, &v);
 64         g.addEdge(u, v);
 65         g.addEdge(v, u);
 66         es[i] = pair<int, int>(u, v);
 67     }
 68 }
 69 
 70 void tarjan(int p, int last_edge) {
 71     dfn[p] = low[p] = ++dfs_clock;
 72     s.push(p);
 73     for (int i = g.h[p], e; ~i; i = g[i].nx) {
 74         e = g[i].ed;
 75         if (i == (last_edge ^ 1))
 76             continue;
 77         if (!dfn[e]) {
 78             tarjan(e, i);
 79             low[p] = min(low[p], low[e]);
 80         } else
 81             low[p] = min(low[p], dfn[e]);
 82     }
 83 
 84     if (low[p] == dfn[p]) {
 85         int cur;
 86         do {
 87             cur = s.top();
 88             s.pop();
 89             low[cur] = low[p];
 90         } while (cur != p);
 91     }
 92 }
 93 
 94 inline void solve() {
 95     for (int i = 1; i <= n; i++)
 96         if (!dfn[i])
 97             tarjan(i, -1);
 98     for (int i = 1; i <= m; i++) {
 99         int u = es[i].first, v = es[i].second;
100         if (low[u] != low[v])
101             deg[low[u]]++, deg[low[v]]++;
102     }
103     
104     int cnt_leaf = 0;
105     for (int i = 1; i <= n; i++)
106         if (dfn[i] == low[i] && deg[low[i]] == 1)
107             cnt_leaf++;
108     printf("%d\n", (cnt_leaf + 1) >> 1);
109 }
110 
111 int main() {
112     init();
113     solve();
114     return 0;
115 }
Edge Biconnected Component

求強連通份量

  有向圖稍微要麻煩一點,不過基本思想仍是同樣的。

  仍然考慮有向圖的dfs生成森林,$dfn$數組以及$low$數組。

  可是有向圖中生成森林會複雜許多:

  在更新$low$的時候要注意兩個點是否在同一個子樹內。

  不難注意到一個強連通份量必定是某一個dfs子樹內,因此咱們仍然考慮一個強連通份量內的最淺點。

  不可貴到它的充分必要條件時$dfn_u = low_u$。必要性是由於,若是它可以到達它的若干級祖先,那麼這一條鏈再加上這一條返祖邊就能獲得一個強連通子圖,充分性顯然,由於它本身就能構成一個強連通子圖。

  咱們用相似於求邊雙的方法,就能夠求出每一個強連通份量內的全部點。   

  1 /**
  2  * hdu
  3  * Problem#1269
  4  * Accepted
  5  * Time: 46ms
  6  * Memory: 3944k
  7  */
  8 #include <iostream>
  9 #include <cstdlib>
 10 #include <cstdio>
 11 #include <vector>
 12 #include <stack>
 13 using namespace std;
 14 typedef bool boolean;
 15 
 16 template <typename T>
 17 void pfill(T* pst, const T* ped, T val) {
 18     for ( ; pst != ped; *(pst++) = val);
 19 }
 20 
 21 const int N = 1e4 + 5;
 22 
 23 typedef class Edge {
 24     public:
 25         int ed, nx;
 26 
 27         Edge(int ed = 0, int nx = 0):ed(ed), nx(nx)    {    }
 28 }Edge;
 29 
 30 typedef class MapManager {
 31     public:
 32         int h[N];
 33         vector<Edge> es;
 34 
 35         void addEdge(int u, int v) {
 36             es.push_back(Edge(v, h[u]));
 37             h[u] = (signed) es.size() - 1;
 38         }
 39 
 40         Edge& operator [] (int p) {
 41             return es[p];
 42         }
 43 }MapManager;
 44 
 45 #define pii pair<int, int>
 46 
 47 int n, m;
 48 MapManager g;
 49 stack<int> s;
 50 int dfs_clock;
 51 int dfn[N], low[N];
 52 boolean instack[N];
 53 
 54 inline boolean init() {
 55     scanf("%d%d", &n, &m);
 56     if (!n && !m)
 57         return false;
 58     g.es.clear();
 59     dfs_clock = 0;
 60     pfill(dfn, dfn + n + 1, 0);
 61     pfill(g.h, g.h + n + 1, -1);
 62     for (int i = 1, u, v; i <= m; i++) {
 63         scanf("%d%d", &u, &v);
 64         g.addEdge(u, v);
 65     }
 66     return true;
 67 }
 68 
 69 int cnt_scc = 0;
 70 void tarjan(int p) {
 71     dfn[p] = low[p] = ++dfs_clock;
 72     instack[p] = true;
 73     s.push(p);
 74     for (int i = g.h[p], e; ~i; i = g[i].nx) {
 75         e = g[i].ed;
 76         if (!dfn[e]) {
 77             tarjan(e);
 78             low[p] = min(low[e], low[p]);
 79         } else if (instack[e])
 80             low[p] = min(dfn[e], low[p]);
 81     }
 82 
 83     if (low[p] == dfn[p]) {
 84         int cur;
 85         do {
 86             cur = s.top();
 87             s.pop();
 88             instack[cur] = false;
 89         } while (cur != p);
 90         cnt_scc++;
 91     }
 92 }
 93 
 94 inline void solve() {
 95     cnt_scc = 0;
 96     for (int i = 1; i <= n; i++)
 97         if (!dfn[i])
 98             tarjan(i);
 99     puts((cnt_scc == 1) ? ("Yes") : ("No"));
100 }
101 
102 int main() {
103     while (init())
104         solve();
105     return 0;
106 }
Strongly Conneceted Component
相關文章
相關標籤/搜索