• 浅谈Tarjan算法


    预备知识

      设无向图$G_{0} = (V_{0}, E_{0})$,其中$V_{0}$为定点集合,$E_{0}$为边集,设有向图$G_{1} = (V_{1}, E_{1})$,其中$V_{1}$为定点集合,$E_{1}$为边集。

    • 无向图中的路径:如果存在一个顶点序列$v_{p},v_{i_{1}},cdots,v_{i_{k}},v_{q}$,使得$left ( v_{p}, v_{i_{1}} ight ),left ( v_{i_{1}},v_{i_{2}} ight ),cdots,left ( v_{k-1}, v_{k} ight ),left ( v_{k}, v_{q} ight )in E_{0}$,那么称$v_{p}$到$v_{q}$之间存在一条路径。
    • 有向图中的路径:如果存在一个顶点序列$v_{p},v_{i_{1}},cdots,v_{i_{k}},v_{q}$,使得$left ( v_{p}, v_{i_{1}} ight ),left ( v_{i_{1}},v_{i_{2}} ight ),cdots,left ( v_{k-1}, v_{k} ight ),left ( v_{k}, v_{q} ight )in E_{1}$,那么称$v_{p}$到$v_{q}$之间存在一条路径。
    • 连通图:在无向图中,对于任意有序对$left ( v_{i},v_{j} ight )in(V_{0} imes V_{0})$,都存在一条$v_{i}$到$v_{j}$的路径。
    • 强连通图:在有向图中,对于任意有序对$left ( v_{i},v_{j} ight )in(V_{1} imes 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),可以理解为是第几个被搜索到的节点。

      $low_i$:在点$i$的dfs子树中通过1条返祖边到达的最早祖先。

      Tarjan算法首先会对原图进行深度优先搜索。

      当从一个访问过的点通过边$e$到达一个未访问的点,则将边$e$标记为树边。如果一条非树边$(u, v)$使得要么$u$是$v$的祖先满足,要么$v$是$u$的祖先,那么称边$(u, v)$是一条返祖边

      显然当遇到一条返祖边时,需要用它来更新当前点的$low$值

      通过Tarjan算法得到的生成森林是dfs生成森林:

      (其中虚边是返祖边)

    Tarjan算法的应用

    求割点和割边

      首先给出一个结论

    定理1 无向图中的每一条边,要么是树边,要么是返祖边。

      证明 假如存在其他边,它满足它不连它的祖先也不连它的后代,那么它一定是满足:

      然后根据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() != '
    ') {
    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
    ", 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
    ", res);
    93 }
    94 
    95 int main() {
    96     while (init())
    97         solve();
    98     return 0;
    99 }
    Cut Edge

    求点-双连通分量

       在求点-双连通分量(以下简称为点双)之前,我们再来证明一个东西:

    定理3 每条边恰好属于一个点双。

      证明 首先来说明每条边一定属于一个点双连通子图。考虑这条边的两个端点以及它本身构成的子图,显然它是点双连通的。

      然后来说明任意两个点双没有公共边。

      假设存在两个点双有一条公共边$(u, v)$,假设它们的点集分别为$V_1, V_2$。如果删掉的点$xin V_1$或$xin V_2$,且$x eq u, x eq 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
    ", 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
    ", 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
    ", (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
  • 相关阅读:
    python写入excel(xlswriter)--生成图表
    使用jmeter 进行http 接口测试
    Ant 批量执行jmeter 脚本
    搭建持续集成接口测试平台(jenkins+ant+jmeter)
    java异常
    java之线程
    android开发之背景音乐与音效
    android开发之存储数据
    android开发之自定义组件
    android开发之生命周期
  • 原文地址:https://www.cnblogs.com/yyf0309/p/8452173.html
Copyright © 2020-2023  润新知