算法随笔——tarjan

随便记点
链接

有向图求强连通分量(SCC)

SCC 定义:任意两个点都可以互相到达的子图。
一张图中每个节点只属于一个 SCC。

\(dfn\) 时间戳,\(low\) 追溯值,表示 \(subtree(X)\) 能到达的点的最小时间戳且点在栈中(标准意义上来说是只经过一条边,实际没有影响)。

dfs 过程中维护一个栈,表示当前可能凑成一个 SCC 的元素。

具体过程

  1. dfs,x 被第一次访问,\(low[x] = dfn[x]\),入栈
  2. 扫描出边 (x,y),
    • y 没被访问,\(low[x] = min(low[x],low[y])\) ——根据定义可知。
    • y 访问过且于栈内(即不属于任何一个SCC,\(low[x] = min(low[x],dfn[y])\)
  3. 扫描完后,若有 \(low[x] = dfn[x]\),说明子树出现横叉边,与 x 构成了环。则栈顶一直到 \(x\) 之间的元素为一个 \(SCC\)

缩点

可以将每一个 \(SCC\) 看作一个点,则新建出一个有向无环图 (\(DAG\))。
image
值得注意的是建 \(DAG\) 过程中有重边并不会影响拓扑排序。

重要结论!!!

代码中 \(SCC\) 的编号递减顺序即为 \(DAG\) 的拓扑序。
因此有的时候跑完 tarjan 并不需要在跑一次 topo
非常的牛。

code

void tarjan(int u)
{
	dfn[u] = low[u] = ++tot;
	sta.push(u);ins[u] = 1; //标记是否在栈中
	for (auto j : v[u])
	{
		if (!dfn[j]) tarjan(j),low[u] = min(low[u],low[j]);
		else if (ins[j]) low[u] = min(low[u],dfn[j]);
	}	
	if (low[u] == dfn[u])
	{
		cnt ++;
		int cur;
		do
		{
			cur = sta.top();
			scc[cnt].push_back(cur),sta.pop();ins[cur] = 0;
			id[cur] = cnt;
		}while (cur != u);
	}
}

for (int i = 1;i <= n;i++) if (!dfn[i]) tarjan(i); //图可能不联通
for (int i = 1;i <= n;i++)
	for (auto j : v[i])
		if (c[i] != c[j]) v2[c[i]].push_back(c[j]);

无向图求联通分量

边双连通分量

相当于把图上的桥去掉。
判定方法:

posted @ 2024-04-15 00:04  codwarm  阅读(9)  评论(0)    收藏  举报