設無向圖$G_{0} = (V_{0}, E_{0})$,其中$V_{0}$爲定點集合,$E_{0}$爲邊集,設有向圖$G_{1} = (V_{1}, E_{1})$,其中$V_{1}$爲定點集合,$E_{1}$爲邊集。node
$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
首先給出一個結論code
定理1 無向圖中的每一條邊,要麼是樹邊,要麼是返祖邊。component
證實 假如存在其餘邊,它知足它不連它的祖先也不連它的後代,那麼它必定是知足:
而後根據dfs的性質和無向邊的性質,容易獲得不存在這種狀況。
假如如今考慮點$p$是否是割點,分兩種狀況討論:
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 }
求橋的話,相對就簡單一些。
一個很是顯然的結論:
定理2 返祖邊不多是橋。
證實 由於返祖邊的兩端必定經過樹邊連通。因此刪掉返祖邊不會改變圖的連通性。
所以割邊的個數不會超過$n - 1$(一個顯然,但沒多大用的性質)。
我更想說的是,再根據定理1能夠獲得橋必定是樹邊。
考慮什麼樣的樹邊被斷掉後圖的連通份量的個數增長。假如這條樹邊兩端的點是$u, v$,其中$u$是$v$的爸爸父節點。刪掉邊$(u, v)$後,連通份量個數增長的充分必要條件是$v$沒法經過返祖邊到達$u$或者$u$的祖先。所以,不可貴到條件是$low_v = dfn_v$。
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 }
在求點-雙連通份量(如下簡稱爲點雙)以前,咱們再來證實一個東西:
定理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$的時候,這條邊是這個點雙內的最後一條邊。
若是須要求出點雙內的全部點和全部邊能夠用一個棧記錄一下邊。
注意一下返祖邊只在子節點的時候加入,這個能夠經過判斷深度優先數的大小解決掉(你必定也不但願在其餘某個點雙內莫名其妙多出一條邊)。
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 }
有時候咱們但願求出點雙內的點,這個時候棧內記錄邊略顯得麻煩,能夠考慮找到一個點的時候加入一個點,它在找到包含它到它的父節點的那條樹邊的點雙時被彈出棧。具體細節能夠見代碼。
/** * 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$,那麼再加入它的某個父節點,那麼它將成爲割點。
若是須要求出邊雙內的全部點再用一個棧記錄一下點就行了。
彷佛還有一種作法是邊雙內必定不包含原圖的橋,所以咱們找到全部的橋,把它們刪掉就獲得了全部邊雙了。這個條件只是必要性,它的充分性能夠考慮若是它刪掉後使得連通塊個數增長,那麼它必定是原圖的橋。(它是原圖的一個子圖,那麼再加入若干端點都在它內部邊後不會使得它的邊連通性下降)。
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 }
有向圖稍微要麻煩一點,不過基本思想仍是同樣的。
仍然考慮有向圖的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 }