强联通分量:Kosarajus算法
以前接触过,也花了不少时间弄懂,不过当时只是写了一道题,代码也不知道放哪了,几个月下来忘得差不多了。
今日回顾,有温故而知新的感觉。
算法的证明与理解如下,具体的网上/书上都有(摘自《数据结构与算法分析》)
由于V是X在Gr的深度优先搜索树中的一个后裔,因此存在Gr中一条从X到V的路径,从而存在G中中一条从V到X的路径。此外,由于X是根节点,因此X从第一次深度优先搜索得到更高的后续编号。于是,在第一次深度优先搜索期间所有处理V的工作都在X的工作结束前完成。既然存在一条从V到X的路径,因此V必然是X在G的生成树中的一个后裔——否则V将在X之后结束。这意味着G中从X到V有一条路径,证明完成。
我写的代码很丑,不知道怎么命名反向边及相关操作,于是就在后面加了一个r。
下面是POJ的三道入门题,基本一样,第一道用邻接矩阵存储,后两道用邻接表存储,都是最简单的缩点然后求出入度有关的。具体的写在注释里了。
POJ 1236
{ http://www.cppblog.com/mythit/archive/2009/05/25/85718.html 这里写得不错,就复制一下吧 题目大意:N(2<N<100)各学校之间有单向的网络,每个学校得到一套软件后,可以通过单向网络向周边的学校传输,问题1:初始至少需要向多少个学校发放软件,使得网络内所有的学校最终都能得到软件。2,至少需要添加几条传输线路(边),使任意向一个学校发放软件后,经过若干次传送,网络内所有的学校最终都能得到软件。 具体算法:先用Korasaju Algorithm求出有向图所有的强连通分量,然后将所有的强连通分量缩成一个点(缩点),这样原来的有向图就缩成了一个DAG图(有向无环图);用2个数组分别记录新生成的DAG图中的每个顶点(包括原来的顶点和强连通分量的缩点)是否有出边和入边,最后遍历每个顶点,如果没有入边,则ans1++;如果没有出边,ans2++。最后所求即为ans1和max(ans1,ans2)。 犯错:第二次DFS顺序弄反了,另外第一个问题不能用输出强联通分量个数,因为不同强联通分量之间是可以传递的。 2011-07-14 09:50 } #include <stdio.h> #define MAXN 200 int g[MAXN][MAXN]; int gr[MAXN][MAXN]; int in[MAXN],out[MAXN],order[MAXN]; int belong[MAXN]; int vis[MAXN]; int count, num, i, j, k, n; int max(int a, int b) { return a > b ? a : b; } void init() { int i, j; scanf("%d", &n); for (i = 1; i <= n; i++) while (scanf("%d", &j) && j) { g[i][j] = 1; gr[j][i] = 1; } } void dfs(int v) { vis[v] = 1; int i; for (i = 1; i <= n; i++) if ((!vis[i]) && g[v][i]) dfs(i); order[++num] = v; } void dfsr(int v) { vis[v] = 1; belong[v] = count; int i; for (i = 1; i <= n; i++) if ((!vis[i]) && gr[v][i]) dfsr(i); } void kosaraju() { for (i = 1; i <= n; i++) if (!vis[i]) dfs(i); memset(vis, 0, sizeof(vis)); for (i = n; i >= 1; i--) if (!vis[order[i]]) { count ++; dfsr(order[i]); } } void calc() { int i, j, leaf = 0, root = 0; for (i = 1; i <= n; i++) for (j = 1; j <= n; j++) if (g[i][j] && belong[i] != belong[j]) { in[belong[j]]++; out[belong[i]]++; } for (i = 1; i <= count; i++) { if (!in[i]) root++; if (!out[i]) leaf++; } if (count == 1) printf("1\n0\n"); else printf("%d\n%d\n", root, max(root, leaf)); } int main() { init(); kosaraju(); calc(); return 0; }
POJ 2186
/* 来自http://www.cppblog.com/RyanWang/archive/2009/02/26/74984.html 题目简述: n头奶牛,给出若干个欢迎关系a b,表示a欢迎b, 欢迎关系是单向的,但是是可以传递的。 另外每个奶牛都是欢迎他自己的。 求出被所有的奶牛欢迎的奶牛的数目。 模型转换: N个顶点的有向图,有M条边(N≤10000,M≤50000)。 求一共有多少个点,满足这样的条件: 所有其它的点都可以到达这个点。 算法:求强连通分量,计算每个分量的出度,如果只有一个为0则该强联通分量的节点个数就是答案,否则无解。 */ #include <stdio.h> #define MAXN 100000 #define MAXM 1000000 int e[MAXM],next[MAXM]; int er[MAXM],nextr[MAXM]; int g[MAXN],gr[MAXN]; int size, sizer; int n, m, i, j, k, x, y, count, number; int out[MAXN]; int order[MAXN]; int vis[MAXN]; int belong[MAXN]; int num[MAXN]; void insert(int x, int y) { e[++size] = y; next[size] = g[x]; g[x] = size; } void insertr(int x, int y) { er[++sizer] = y; nextr[sizer] = gr[x]; gr[x] = sizer; } void init() { scanf("%d%d", &n, &m); for (i = 1; i <= m; i++) { scanf("%d%d", &x, &y); insert(x, y); insertr(y, x); } } void dfs(int v) { vis[v] = 1; int p; for (p = g[v]; p; p = next[p]) if (!vis[e[p]]) dfs(e[p]); order[++number] = v; } void dfsr(int v) { vis[v] = 1; belong[v] = count; int p; for (p = gr[v]; p; p = nextr[p]) if (!vis[er[p]]) dfsr(er[p]); } void kosaraju() { for (i = 1; i <= n; i++) if (!vis[i]) dfs(i); memset(vis, 0, sizeof(vis)); for (i = n; i >= 1; i--) if (!vis[order[i]]) { count++; dfsr(order[i]); } } int calc() { int i, j, k, p, ans = 0; for (i = 1; i <= n; i++) num[belong[i]]++; for (i = 1; i <= n; i++) for (p = g[i]; p; p = next[p]) if (belong[e[p]] != belong[i]) out[belong[i]]++; for (i = 1; i <= count; i++) { if (ans && !out[i]) return 0; if (!out[i]) ans = num[i]; } return ans; } int main() { init(); kosaraju(); printf("%d\n", calc()); return 0; }
POJ 2553
/* 题目出得比较恶心,下了一大堆定义。总之就是求所有满足这个条件的点: 它能到达的点也能到达它。于是可以进行强连通分量缩点, 只要该点满足所属的强连通分量的出度为0即为所求点。 细节的错误犯了一些,有时候打错一个变量真的很难找啊。 2011年7月14日14:01:57 */ #include <stdio.h> #define MAXN 100000 #define MAXM 1000000 int e[MAXM],er[MAXM],next[MAXM],nextr[MAXM]; int g[MAXN],gr[MAXN]; int order[MAXN],vis[MAXN],out[MAXN],belong[MAXN]; int m, n, i, j, k, x, y; int count, num; int size, sizer; void insert(int x, int y) { e[++size] = y; next[size] = g[x]; g[x] = size; } void insertr(int x, int y) { er[++sizer] = y; nextr[sizer] = gr[x]; gr[x] = sizer; } void dfs(int v) { vis[v] = 1; int p; for (p = g[v]; p; p = next[p]) if (!vis[e[p]]) dfs(e[p]); order[++num] = v; } void dfsr(int v) { vis[v] = 1; belong[v] = count; int p; for (p = gr[v]; p; p = nextr[p]) if (!vis[er[p]]) dfsr(er[p]); } void kosaraju() { for (i = 1; i <= n; i++) if (!vis[i]) dfs(i); memset(vis, 0,sizeof(vis)); for (i = n; i >= 1; i--) if (!vis[order[i]]) { count++; dfsr(order[i]); } } void init() { memset(g, 0, sizeof(g)); memset(gr, 0, sizeof(gr)); memset(belong, 0, sizeof(belong)); memset(order, 0, sizeof(order)); memset(vis, 0, sizeof(vis)); memset(out, 0, sizeof(out)); size = 0; sizer = 0; num = 0; for(i = 1; i <= m; i++) { scanf("%d%d", &x, &y); insert(x, y); insertr(y, x); } } void calc() { int i, p; // for (i = 1; i <= n; i++) // printf("%d ",belong[i]); for (i = 1; i <= n; i++) for (p = g[i]; p; p = next[p]) if (belong[i] != belong[e[p]]) out[belong[i]]++; for (i = 1; i <= n; i++) if (!out[belong[i]]) printf("%d ",i); printf("\n"); } int main() { scanf("%d", &n); while (n != 0) { scanf("%d", &m); init(); kosaraju(); calc(); scanf("%d", &n); } return 0; }