tarjan有向图的强连通分量

  有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。

  通过对强连通分量的缩点,可以将任意一个有向图变成一个有向无环图(DAG)。

我们将边分为四类:1.树枝边(x是y的父亲结点)2.前向边(x是y的祖先结点)3.后向边(x是y的子孙结点)4.横叉边(连向其他分支的并且已经搜过的边)

可以看出,树枝边是前向边的特殊情况。

如何判断x所在的位置在哪个强连通分量中?

情况1:存在一条边后向边,指向祖宗结点。

情况2:先由该点通过横叉边走到另一个分支,再由分支走到该点的某个祖宗结点上。

这里用tarjan算法求强连通分量。我们引入一个时间戳的概念,如上图,用dfs对其进行编号。

扫描二维码关注公众号,回复: 8051054 查看本文章

对每个点定义两个时间戳dfn[u]和low[u]表示从u开始走,所能遍历到的最小时间戳是什么。如果u是其所在的强联通分量重的最高点,等价于dfn[u] == low[u]

可以证明,通过dfs搜图,能得到该图拓扑图的逆序,tarjan就是按照dfs的顺序搜索,所以按照连通分量编号递减的顺序一定是拓扑序。

最受欢迎的牛

由题意,缩点后必须只有一个点是终点,该终点的连通分量的个数就是能被全部牛认同的牛的数量。如果有两个终点,因为是DAG,两个终点不能互相到达(认同),所以就答案就为0。

代码:

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstring>
 4 #include <cstdio>
 5 
 6 using namespace std;
 7 
 8 const int N = 10010, M = 50010;
 9 
10 int e[M], ne[M], h[M], idx;
11 int stk[N], in_stk[N], size[N];
12 int scc_cnt, timestamp, top;
13 int dfsn[N], low[N];
14 int dout[N], id[N];
15 int n, m;
16 
17 void add(int a, int b)
18 {
19     e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
20 }
21 
22 void tarjan(int u)
23 {
24     dfsn[u] = low[u] = ++timestamp;
25     stk[++ top] = u, in_stk[u] = true;
26     for(int i = h[u] ; ~i ; i = ne[i])
27     {
28         int j = e[i];
29         if(!dfsn[j])
30         {
31             tarjan(j);
32             low[u] = min(low[u], low[j]);
33         }
34         else if(in_stk[j])low[u] = min(low[u], dfsn[j]);
35     }
36     
37     if(dfsn[u] == low[u])
38     {
39         ++scc_cnt;
40         int y;
41         do{
42             y = stk[top --];
43             in_stk[y] = false;
44             id[y] = scc_cnt;
45             size[scc_cnt] ++;
46         }while(y != u);
47     }
48 }
49 
50 int main(){
51     scanf("%d%d", &n, &m);
52     
53     memset(h, -1, sizeof h);
54     
55     while(m --)
56     {
57         int a, b;
58         scanf("%d%d", &a, &b);
59         add(a, b);
60     }
61     
62     for(int i = 1 ; i <= n ; i ++)
63         if(!dfsn[i])
64             tarjan(i);
65     
66     for(int i = 1 ; i <= n ; i ++)
67         for(int j = h[i] ; ~j ; j = ne[j])
68         {
69             int k = e[j];
70             int a = id[i], b = id[k];
71             if(a != b)dout[a] ++;
72         }
73     
74     int zeros = 0, sum = 0;//zeros记录终点的数量,即出度为0的点个数,sum存答案
75     for(int i = 1 ; i <= scc_cnt; i ++)
76         if(!dout[i])
77         {
78             zeros ++;
79             sum += size[i];
80             if(zeros > 1)
81             {
82                 sum = 0;
83                 break;
84             }
85         }
86     
87     printf("%d\n", sum);
88     
89     return 0;
90 }

 学校网络

根据题意,第一问求的是最少放几个点,第二问求的是最少加多少边可以变成强连通图,可以推出进行tarjan缩点后,第一个答案就是起点的个数|P|,第二个答案当|P| = 1时,为终点个数|Q|,当|P| > 1时,答案为max(|P|, |Q|)。

证明:首先,|P| <= |Q|,如果起点的个数大于终点个数,因为缩点后的图是DAG每个起点一定会走到一个终点,所以必然有不同的起点走到相同的终点,所以是可以只给其中一个起点,所以有|P| <= |Q|。

当|P| = 1时,每个终点都要连一条边回起点构成强连通,答案为|Q|。当|P| > 1,因为|Q| >= |P| > 1,所以一定有至少两组点,P1 -> Q1, P2 - > Q2,是两组不同的点,如果走到的不是两个终点,那么就是说所有起点都是走到同一个终点,而|Q|至少是2,至少还要存在另一个终点,矛盾。所以有至少两组点,P1 -> Q1, P2 - > Q2。将Q1连一条边到P2,|P’| = |P| - {P2}, |Q’| = |Q| - {Q2},对起点进行|P| - 1次,增加|P| - 1条边,起点个数变为1,终点个数变为|Q| - (|P| - 1)。此时再将需要由终点向起点连 |Q| - (|P| - 1)条边,加上前面加的|P| - 1条边,最后边数为|Q|。如果|P|是大于|Q|的,那么增加边数就是|P|。

代码:

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstring>
 4 #include <cstdio>
 5 
 6 using namespace std;
 7 
 8 const int N = 110, M = N * N;
 9 
10 int e[M], h[M], ne[M], idx;
11 int stk[N], in_stk[N], top;
12 int dfn[N], low[N], timestamp, scc_cnt, id[N];
13 int dout[N], din[N];
14 int n;
15 
16 void add(int a, int b)
17 {
18     e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
19 }
20 
21 void tarjan(int u)
22 {
23     dfn[u] = low[u] = ++ timestamp;
24     stk[++ top] = u, in_stk[u] = true;
25     for(int i = h[u] ; ~i ; i = ne[i])
26     {
27         int j = e[i];
28         if(!dfn[j])
29         {
30             tarjan(j);
31             low[u] = min(low[u], low[j]);
32         }
33         else if(in_stk[j])low[u] = min(low[u], dfn[j]); 
34     }
35     
36     if(dfn[u] == low[u])
37     {
38         ++ scc_cnt;
39         int y;
40         do{
41             y = stk[top --];
42             in_stk[y] = false;
43             id[y] = scc_cnt;
44         }while(y != u);
45     }
46 }
47 
48 int main(){
49     cin >> n;
50     memset(h, -1, sizeof h);
51     for(int i = 1 ; i <= n ; i ++)
52     {
53         int t;
54         while(cin >> t , t)add(i, t);
55     }
56     
57     for(int i = 1 ; i <= n ; i ++)
58         if(!dfn[i])
59             tarjan(i);
60             
61     for(int i = 1 ; i <= n ;i ++)
62         for(int j = h[i] ; ~j ; j = ne[j])
63         {
64             int k = e[j];
65             int a = id[i], b = id[k];
66             if(a != b)
67             {
68                 dout[a] ++;
69                 din[b] ++;
70             }
71         }
72     
73     int a = 0, b = 0;
74     for(int i = 1 ; i <= scc_cnt ; i ++)
75     {
76         if(!din[i])a ++;
77         if(!dout[i])b ++;
78     }
79         
80     printf("%d\n", a);
81     if(scc_cnt == 1)puts("0");
82     else printf("%d\n", max(a, b));
83     
84     return 0;
85 }

银河

这题实际上可以用差分约束做。

 但是因为这个图的所有边权都是大于等于0的,所以可以用强连通分量。

根据题意,如果图中存在正环,则无解。而对于强连通分量来说,正环必定在强连通分量内,由于强连通分量内的点都可以相互到达,所以只要有一条边的权值大于0, 那么就存在正环,如果不存在正环,则所以边权都为0,所有点都相同。可以看做一个点。其次强连通分量内的所有点的最终距离是一样的。所以用tarjan缩点后,在拓扑图上跑一遍最长路径即可。

代码:

  1 #include <iostream>
  2 #include <algorithm>
  3 #include <cstring>
  4 #include <cstdio>
  5 
  6 using namespace std;
  7 
  8 typedef long long LL;
  9 
 10 const int N = 100010, M = 600010;//建双向200000,从0点向所有点连边,增加到300000,建两次图600000
 11 
 12 int e[M], ne[M], h[M], hs[M], w[M], idx;
 13 int stk[N], in_stk[N], dfn[N], low[N], timestamp, top, scc_cnt, id[N], size[N];
 14 int dis[N];
 15 int n, m;
 16 
 17 void add(int h[], int a, int b, int c)
 18 {
 19     e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
 20 }
 21 
 22 void tarjan(int u)
 23 {
 24     dfn[u] = low[u] = ++ timestamp;
 25     stk[ ++ top] = u, in_stk[u] = true;
 26     for(int i = h[u] ; ~i ; i = ne[i])
 27     {
 28         int j = e[i];
 29         if(!dfn[j])
 30         {
 31             tarjan(j);
 32             low[u] = min(low[u], low[j]);
 33         }
 34         else if(in_stk[j])low[u] = min(low[u], dfn[j]);
 35     }
 36     if(low[u] == dfn[u])
 37     {
 38         ++ scc_cnt;
 39         int y;
 40         do{
 41             y = stk[top --];
 42             in_stk[y] = false;
 43             id[y] = scc_cnt;
 44             size[scc_cnt] ++;
 45         }while(y != u);
 46     }
 47 }
 48 
 49 int main(){
 50     cin >> n >> m;
 51     
 52     memset(h, -1, sizeof h);
 53     memset(hs, -1, sizeof hs);
 54     
 55     for(int i = 1 ; i <= n ; i ++)add(h, 0, i, 1);
 56     
 57     while (m -- )
 58     {
 59         int t, a, b;
 60         scanf("%d%d%d", &t, &a, &b);
 61         if (t == 1) add(h, b, a, 0), add(h, a, b, 0);
 62         else if (t == 2) add(h, a, b, 1);
 63         else if (t == 3) add(h, b, a, 0);
 64         else if (t == 4) add(h, b, a, 1);
 65         else add(h, a, b, 0);
 66     }
 67     
 68     tarjan(0);
 69     
 70     bool success = true;//判断有无解并建第二次图
 71     for(int i = 0 ; i <= n ; i ++)
 72     {
 73         for(int j = h[i] ; ~j ; j = ne[j])
 74         {
 75             int k = e[j];
 76             int a = id[i], b = id[k];
 77             if(a == b)
 78             {
 79                 if(w[j] > 0)
 80                 {
 81                     success = false;
 82                     break;
 83                 }
 84             }
 85             else add(hs, a, b, w[j]);
 86         }
 87         if(!success)break;
 88     }
 89     
 90     if(!success)puts("-1");
 91     else
 92     {
 93         for(int i = scc_cnt ; i  ; i --)
 94             for(int j = hs[i] ; ~j ; j = ne[j])
 95             {
 96                 int k = e[j];
 97                 dis[k] = max(dis[k], dis[i] + w[j]);
 98             }
 99         
100         LL res = 0;
101         for(int i = 1 ; i <= scc_cnt ; i ++)res += (LL)dis[i] * size[i];//求最长路所以连通分量里面都要加上
102         
103         printf("%lld\n", res);
104     }
105     return 0;
106 }

猜你喜欢

转载自www.cnblogs.com/1-0001/p/11968228.html