算法随笔——tarjan
随便记点
链接
有向图求强连通分量(SCC)
SCC 定义:任意两个点都可以互相到达的子图。
一张图中每个节点只属于一个 SCC。
\(dfn\) 时间戳,\(low\) 追溯值,表示 \(subtree(X)\) 能到达的点的最小时间戳且点在栈中(标准意义上来说是只经过一条边,实际没有影响)。
dfs 过程中维护一个栈,表示当前可能凑成一个 SCC 的元素。
具体过程
- dfs,x 被第一次访问,\(low[x] = dfn[x]\),入栈
- 扫描出边 (x,y),
- y 没被访问,\(low[x] = min(low[x],low[y])\) ——根据定义可知。
- y 访问过且于栈内(即不属于任何一个SCC,\(low[x] = min(low[x],dfn[y])\)
- 扫描完后,若有 \(low[x] = dfn[x]\),说明子树出现横叉边,与 x 构成了环。则栈顶一直到 \(x\) 之间的元素为一个 \(SCC\)。
缩点
可以将每一个 \(SCC\) 看作一个点,则新建出一个有向无环图 (\(DAG\))。

值得注意的是建 \(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]);
无向图求联通分量
边双连通分量
相当于把图上的桥去掉。
判定方法:

浙公网安备 33010602011771号