「学习笔记」强联通分量

一.强连通分量的相关概念

  • 强连通图

    在一个有向图中,存在一条路径,使得所有的节点都被经过至少一次,那么这样的图称作强连通图。下图就是一个强连通图:
    image

  • 强联通分量

    在强联通图的基础上加入一些点和路径,使得现在的图不再强联通,那么原来强联通的部分称作强连通分量。下图绿色部分就是强联通分量:
    image

二.强联通分量的作用

在解决图论问题时,我们可以利用强联通分量的知识,将图中的各个强连通分量都缩成一个点,便于解决问题。有时,通过求强连通分量,可以得出图中环及环的长度。

三.\(\text{Tarjan}\) 算法求强联通分量

\(1.\) 基本思路

先考虑一下强连通分量的性质:存在一条路径可以从任意一点出发再到达该点。

在查找过程中,可以对经过的点标记。发现节点 \(u\) 连向的节点 \(v\) 被标记过,那么就说明找到了一条路径,这条路径上的所有节点构成一个强联通分量。为了保存这条路径上的节点,最合适的数据结构就是栈。找到路径时,逐个弹出栈中的元素,直到起始节点。再继续搜索其它强连通分量。

\(2.\) 算法讲解

需要对每一个节点 \(u\) 创建以下两个变量:
\(dfn_u:\) 表示 \(DFS\) 时节点 \(u\) 被搜索到的次序。

\(low_u:\) 表示节点 \(u\) 能够回溯到的最早的在栈中的节点。用定义来解释:设以节点 \(u\) 为根的子树为 \(subtree_u\),那么 \(low_u = \min_{(dfn_v)}\) \((v \in subtree_u)\)

然后我们就能轻易得出三个性质:

  • \(1.\)\(u\) 为根节点的子树中的任意一个节点的 \(dfn\) 值都小于 \(dfn_u\)

  • \(2.\) 从根出发的一条路径上的 \(dfn\) 值严格递增。

  • \(3.\) 从根出发的一条路径上的 \(low\) 值严格非降。

接下来 \(DFS\) 图中的所有节点。

搜索过程中,对于相邻的节点 \(u\)\(v\),考虑以下三种情况:

  • \(1.\) 节点 \(v\) 未被访问过:继续对节点 \(v\) 进行 \(DFS\)。回溯过程,用 \(low_v\) 更新 \(low_u\)。因为存在节点 \(u\)\(v\) 的直接路径,那么节点 \(u\) 能回溯到栈中的节点,节点 \(v\) 必然也能回溯到。

  • \(2.\) 节点 \(v\) 被访问过,已经在栈中:用 \(dfn_v\) 来更新 \(low_u\)

  • \(3.\) 节点 \(v\) 被访问过,但不在栈中:说明 \(v\) 被搜索完毕,其所在的强联通分量已经被处理,所以不用被其进行操作。

对于一个强连通分量图,容易得到,在该图中只有一个节点 \(u\) 满足 \(low_u=dfn_u\),且该点一定是在 \(DFS\) 过程中被访问的第一个节点。因为它的 \(dfn\)\(low\) 值最小,不会被该强连通分量中的其他节点所影响。

因此,在回溯的过程中只需要判断 \(dfn_u=low_u\) 是否成立,如果成立,那么节点 \(u\) 以及上方的节点构成一个强连通分量。

\(3.\) 代码实现

stack<int> sta;
vector<int> SCC[N];//记录强连通分量中的点。
int tim = 0, head[N], dfn[N], low[N], col[N], cnt = 0;
//tim:时间戳。
//col_i:表示第i个点属于的强联通分量编号。
void tarjan (int u) {
	dfn[u] = low[u] = ++ tim;//初始该点的dfn=low=时间戳。
	sta.push (u);//当前节点进栈。
	insta[u] = true;
	for (int i = head[u]; i; i = e[i].nxt) {
		int v = e[i].to;
		if (!dfn[v]) {
			tarjan (v);
			low[u] = min (low[u], low[v]);
			//没有被搜索过,用 low_v 值更新 low_u 值。
		}
		else if (insta[v]) {
			low[u] = min (low[u], dfn[v]);
			//搜索过的节点在栈中,用 dfn_v 值更新 low_u 值。
		}
	}
	
	if (dfn[u] == low[u]) {//找到一个 SCC。
		cnt ++;//编号+1。
		int v;
		while (u != v) {
			v = sta.top ();//不断取栈顶。
			sta.pop ();//出栈。
			insta[v] = false;//不在栈中。
			col[v] = cnt;//v点属于第cnt个SCC中。
			SCC[cnt].push_back (v);//将v点加入第cnt个SCC中。
		}
	}
}

易证,时间复杂度为 \(O(n+m)\)

四.例题讲解

P2341 [USACO03FALL][HAOI2006]受欢迎的牛 G

将爱慕关系建为一个有向图,那么在同一个强连通分量中的牛都互相爱慕。

那我们可以将强连通分量看做一个点,缩点后的奶牛就不会出现互相爱慕的情况了。

由题面可知,只有不爱慕其它奶牛才能当明星,那么我们就要在缩点后的图找出度为 \(0\) 的点。

然后得到两个结论:

\(1.\) 缩点后,如果只有一个点出度为 \(0\),则明星的数量为这个点的强连通分量的大小。

\(2.\) 缩点后,如果有多个点出度为 \(0\),那么没有明星。

这样就解决了。

P2272 [ZJOI2007]最大半连通子图

\(tarjan\) 算法缩点后去重边,题目就变成了求最长链以及最长链个数。

缩点后的图是一个 \(DAG\),拓扑排序跑 \(DP\) 转移即可。

posted @ 2022-02-05 19:57  cyhyyds  阅读(548)  评论(2)    收藏  举报