对于强连通分量的操作与运用作了解释

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

强连通图

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

python求强连通分量个数 如何求图的强连通分量_强联通分量

强联通分量

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

python求强连通分量个数 如何求图的强连通分量_搜索_02

二.强联通分量的作用

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

三.\(\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\)