有向图强连通分量:在有向图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对其进行编号。
对每个点定义两个时间戳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 }