校园网络
- 描述
-
南阳理工学院共有M个系,分别编号1~M,其中各个系之间达成有一定的协议,如果某系有新软件可用时,该系将允许一些其它的系复制并使用该软件。但该允许关系是单向的,即:A系允许B系使用A的软件时,B未必一定允许A使用B的软件。
现在,请你写一个程序,根据各个系之间达成的协议情况,计算出最少需要添加多少个两系之间的这种允许关系,才能使任何一个系有软件使用的时候,其它所有系也都有软件可用。
- 输入
- 第一行输入一个整数T,表示测试数据的组数(T<10)
每组测试数据的第一行是一个整数M,表示共有M个系(2<=M<=100)。
随后的M行,每行都有一些整数,其中的第i行表示系i允许这几个系复制并使用系i的软件。每行结尾都是一个0,表示本行输入结束。如果某个系不允许其它任何系使用该系软件,则本行只有一个0.
- 输出
- 对于每组测试数据,输出最少需要添加的这种允许关系的个数。
- 样例输入
-
1 5 2 4 3 0 4 5 0 0 0 1 0
- 样例输出
-
2
- 来源
- POJ改编
- 解析(转):
- Tarjan算法详解
-
【功能】
Tarjan算法的用途之一是,求一个有向图G=(V,E)里极大强连通分量。强连通分量是指有向图G里顶点间能互相到达的子图。而如果一个强连通分量已经没有被其它强通分量完全包含的话,那么这个强连通分量就是极大强连通分量。
【算法思想】
用dfs遍历G中的每个顶点,通dfn[i]表示dfs时达到顶点i的时间,low[i]表示i所能直接或间接达到时间最小的顶点。(实际操作中low[i]不一定最小,但不会影响程序的最终结果)
程序开始时,order初始化为0,在dfs遍历到v时,low[v]=dfn[v]=order++,
v入栈(这里的栈不是dfs的递归时系统弄出来的栈)扫描一遍v所能直接达到的顶点k,如果 k没有被访问过那么先dfs遍历k,low[v]=min(low[v],low[k]);如果k在栈里,那么low[v]=min(low[v],dfn[k])(就是这里使得low[v]不一定最小,但不会影响到这里的low[v]会小于dfn[v])。扫描完所有的k以后,如果low[v]=dfn[v]时,栈里v以及v以上的顶点全部出栈,且刚刚出栈的就是一个极大强连通分量。
【大概的证明】
1. 在栈里,当dfs遍历到v,而且已经遍历完v所能直接到达的顶点时,low[v]=dfn[v]时,v一定能到达栈里v上面的顶点:
因为当dfs遍历到v,而且已经dfs递归调用完v所能直接到达的顶点时(假设上面没有low=dfn),这时如果发现low[v]=dfn[v],栈上面的顶点一定是刚才从顶点v递归调用时进栈的,所以v一定能够到达那些顶点。
2 .dfs遍历时,如果已经遍历完v所能直接到达的顶点而low[v]=dfn[v],我们知道v一定能到达栈里v上面的顶点,这些顶点的low一定小于 自己的dfn,不然就会出栈了,也不会小于dfn[v],不然low [v]一定小于dfn[v],所以栈里v以其v以上的顶点组成的子图是一个强连通分量,如果它不是极大强连通分量的话low[v]也一定小于dfn[v](这里不再详细说),所以栈里v以其v以上的顶点组成的子图是一个极大强连通分量。
【时间复杂度】
因为所有的点都刚好进过一次栈,所有的边都访问的过一次,所以时间复杂度为O(n+m)
代码如下:
1 // 强连通分量缩点 2 #include <iostream> 3 #include <cstring> 4 #include <cstdio> 5 #include <stack> 6 7 using namespace std; 8 9 const int MAX = 105; 10 int map[MAX][MAX]; 11 int low[MAX], DFN[MAX], IN[MAX], OUT[MAX], instack[MAX], t[MAX]; 12 int n, order, res, ans; 13 stack<int> S; 14 15 void init() 16 { 17 memset(map, 0, sizeof(map)); 18 memset(low, 0, sizeof(low)); 19 memset(DFN, 0, sizeof(DFN)); 20 memset(IN, 0, sizeof(IN)); 21 memset(OUT, 0, sizeof(OUT)); 22 memset(instack, 0, sizeof(instack)); 23 memset(t, 0, sizeof(t)); 24 while(!S.empty()) 25 S.pop(); 26 res = 0; 27 order = 0; 28 } 29 30 int min(int x, int y) 31 { 32 return x < y ? x : y; 33 } 34 35 void tr(int u) 36 { 37 int v; 38 DFN[u] = low[u] = ++order; 39 instack[u] = 1; 40 S.push(u); 41 for (int i = 1; i <= n; i++) 42 { 43 if(map[u][i]) 44 { 45 if(!DFN[i]) 46 { 47 tr(i); 48 low[u] = min(low[u], low[i]); 49 } 50 else if(instack[i]) 51 low[u] = min(low[u], DFN[i]); 52 } 53 } 54 if(DFN[u] == low[u]) 55 { 56 ++res; // res 代表强连通分量的个数 57 do 58 { 59 v=S.top(); 60 S.pop(); 61 instack[v] = 0; 62 t[v] = res; 63 }while(v != u); 64 } 65 } 66 67 void tarjan() 68 { 69 for (int i = 1; i <= n; i++) 70 if(!DFN[i]) 71 tr(i); 72 } 73 74 void solve() 75 { 76 for (int i = 1;i <= n; i++) 77 { 78 for (int j = 1;j <= n; j++) 79 if(map[i][j]) // 统计每个强连通分量缩点的入度和出度 80 { 81 ++IN[t[i]]; 82 ++OUT[t[j]]; 83 } 84 } 85 int xx, yy; 86 xx = yy = 0; 87 for(int i = 1; i <= res; i++) 88 { 89 if(IN[i]==0) 90 xx++; 91 else if(OUT[i]==0) 92 yy++; 93 } 94 ans = xx > yy ? xx : yy; // 结果为缩点后的有向图中出度为0或者入度为0中的大者 95 } 96 97 int main() 98 { 99 int T, x; 100 scanf("%d", &T); 101 while (T--) 102 { 103 init(); 104 scanf("%d",&n); 105 for (int i = 1; i <= n; i++) 106 { 107 while (scanf("%d", &x), x) 108 map[i][x] = 1; 109 } 110 tarjan(); 111 solve(); 112 if(res == 1) 113 printf("0\n"); 114 else 115 printf("%d\n", ans); 116 } 117 return 0; 118 }
注:部分用到强连通分量的题目总结:
POJ 2186 Popular Cows (基础)
POJ 2553 The Bottom of a Graph(alpc OJ 1274)(基础)
POJ 1236 Network of Schools (基础)
2010中南赛 light sources(alpc OJ) (基础)
POJ 2762 Going from u to v or from v to u? (中等,弱连通分量 )
POJ 3160 Father Christmas flymouse(难,DP题)
POJ 1904 King‘s Quest(难,推荐,非缩点,匹配思想与强连通分量的转化)