UNPKG

oi-wiki

Version:
137 lines (89 loc) 3.6 kB
## 简介 在阅读下列内容之前,请务必了解 [图论基础](/graph/basic) 部分。 强连通的定义是:有向图 G 强连通是指,G 中任意两个结点连通。 强连通分量(Strongly Connected Components,SCC)的定义是:极大的强连通子图。 这里想要介绍的是如何来求强连通分量。 ## Kosaraju 算法 Kosaraju 算法依靠两次简单的 dfs 实现。 第一次 dfs,选取任意顶点作为起点,遍历所有为访问过的顶点,并在回溯之前给顶点编号,也就是后序遍历。 第二次 dfs,对于反向后的图,以标号最大的顶点作为起点开始 dfs。这样遍历到的顶点集合就是一个强连通分量。对于所有未访问过的结点,选取标号最大的,重复上述过程。 两次 dfs 结束后,强连通分量就找出来了,Kosaraju 算法的时间复杂度为 $O(n+m)$ ### 实现 ```cpp // g 是原图,g2 是反图 void dfs1(int u) { vis[u] = true; for (int v : g[u]) if (!vis[v]) dfs1(v); s.push_back(v); } void dfs2(int u) { color[u] = sccCnt; for (int v : g2[u]) if (!color[v]) dfs2(v); } void kosaraju() { sccCnt = 0; for (int i = 1; i <= n; ++i) if (!vis[i]) dfs1(i); for (int i = n; i >= 1; --i) if (!color[s[i]]) { ++sccCnt; dfs2(s[i]) } } ``` ## Tarjan 算法 Robert E. Tarjan (1948~) 美国人。 Tarjan 发明了很多很有用的东西,下到 NOIP 上到 CTSC 难度的都有。 【举例子:Tarjan 算法,并查集,Splay 树,Tarjan 离线求 lca(Lowest Common Ancestor,最近公共祖先)等等】 我们这里要介绍的是图论中的 Tarjan 算法,用来处理各种连通性相关的问题。 ### 定义 方便起见,我们先定义一些东西。 `dfn[x]`:结点 x 第一次被访问的时间戳 (dfs number) `low[x]`:结点 x 所能访问到的点的 dfn 值的最小值 这里的树指的是 DFS 树 所有结点按 dfn 排序即可得 dfs 序列 ### DFS 树的性质 一个结点的子树内结点的 dfn 都大于该结点的 dfn。 从根开始的一条路径上的 dfn 严格递增。 一棵 DFS 树被构造出来后,考虑图中的非树边。 前向边 (forward edge):祖先→儿子 后向边 (backward edge):儿子→祖先 横叉边 (cross edge):没有祖先—儿子关系的 注意:横叉边只会往 dfn 减小的方向连接 注意:在无向图中,没有横叉边(为什么?) ### 实现 ```cpp dfs(x) { dfn[x] = low[x] = ++index; S.push(x); instack[x] = true; for each edge(x, y) { if (!dfn[y]) { dfs(y); low[x] = min(low[x], low[y]); } else if (instack[y]) { low[x] = min(low[x], dfn[y]); } } if (dfn[x] == low[x]) { while (1) { t = S.pop(); instack[t] = false; if (t == x) break; } } } ``` (转自维基:<https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm)> 时间复杂度 $O(n+m)$ ## Garbow 算法 ## 应用 我们可以将一张图的每个强连通分量都缩成一个点。 然后这张图会变成一个 DAG(为什么?)。 DAG 好啊,能拓扑排序了就能做很多事情了。 举个简单的例子,求一条路径,可以经过重复结点,要求经过的不同结点数量最多。 ## 推荐题目 [USACO Fall/HAOI 2006 受欢迎的牛](https://www.lydsy.com/JudgeOnline/problem.php?id=1051) [POJ1236 Network of Schools](http://poj.org/problem?id=1236)