强连通分量与双连通分量 复习笔记
同步发布于 \(\text{Luogu}\)
前言
改 Atcoder 的时候遇到了双连通分量,然后想起来这东西我两个月之前学得很不好,所以再来看一下啊。
强连通分量
强连通的定义是:有向图 \(G\) 强连通是指,\(G\) 中任意两个结点连通。
强连通分量(\(\text{Strongly}\) \(\text{Connected}\) \(\text{Components}\),\(\text{SCC}\))的定义是:极大的强连通子图。
然后就是如何求出一张有向图当中的强连通分量。用 \(Tarjan\) 算法。
\(Tarjan\) 算法基于 \(dfs\) 生成树。具体来说,我们在 \(dfs\) 的过程中用栈来维护我们需要对其处理强连通分量的点,并对每个点维护两个值:\(dfn\) 和 \(low\)。
其中:
\(dfn_u\) 表示 \(u\) 在 \(dfs\) 生成树中的编号,也就是它被加进树中的时间。
\(low_u\) 表示 \(u\) 在其子树中能够回溯到的最早被加进树中的点,也就是 \(dfn\) 最小的点。具体一点说,这些点包括 \(u\) 的子树中的点以及它们通过任意一条不在搜索树上的边能够到达的点。
那么我们考虑如何维护这两个值。
il void Tarjan(int u)
\(Tarjan\),启动!(删除
\(dfn\) 很容易得出,就是每次搜到一个新的点 \(u\),把它加进树中的时候,令:
dfn[u]=++idx;
并且,此时 \(u\) 能到达的最早被加进树中的点就是它自己,所以完整的应该是:
dfn[u]=low[u]=++idx;
同时我们将 \(u\) 加进栈,并标记它已入栈:
s.push(u),in_stack[u]=1;
然后我们从 \(u\) 开始访问与它相邻(并且不是它的父亲)的点 \(v\)(因为是有向图所以一般不需要判是不是父亲)。
如果 \(v\) 是新的点,也就是说它不在树中,那么我们就搜索他,并根据 \(low\) 的定义,更新 \(low_u\)(因为显然 \(v\) 在 \(u\) 的子树中,所以 \(v\) 能够回溯到的点,\(u\) 也一定能回溯到):
if(!dfn[v]){
Tarjan(v,u);
low[u]=min(low[u],low[v]);
}
否则,如果 \(v\) 已经在栈中,那么就说明我们回溯到了 \(v\),就更新 \(low_u\),但是不再去搜 \(v\) 了。
else if(in_stack[v])low[u]=min(low[u],dfn[v]);
如果以上两种情况都不是,也就是说 \(v\) 已经被搜索过了,并且已经不在栈中了,那么就说明它已经在另一个强连通分量里面了,那就不管它了。
以上,就是对相邻结点的访问过程。接下来我们要进行一个判断:
if(low[u]==dfn[u])
如果成立,就说明 \(u\) 是当前强连通分量的最上面的点。
解释就是,反证一下,假如此条件成立,但 \(u\) 不是当前强连通分量最上面的点,那就说明在 \(u\) 的子树中,一定还能回溯到更小的点,但是根据 \(low\) 的定义,我们可以把子树中回溯到的更小的点拿来更新 \(low_u\),然后 \(low_u\) 肯定就会比 \(dfn_u\) 小,于是就与此条件矛盾了。
所以如果此条件成立,就说明 \(u\) 是当前强连通分量的最上面的点。也就是说 \(u\) 以及在 \(u\) 之后入栈的点都属于 \(u\) 这个强连通分量,而在 \(u\) 之前入栈的店则不是。那么我们就从栈顶开始,把当前在栈中的点不断弹出,直到弹出 \(u\) 就停止,然后新建一个强连通分量,并将它们(包括 \(u\))都加进去:
if(low[u]==dfn[u]){
int j;
cnt_scc++;
do{
j=s.top();
s.pop();
in_stk[j]=0;
scc[j]=cnt_scc;
}while(j!=u);
}
然后就结束了啊!(附上完整的 \(Code:\)
#define il inline
#define re register
il void Tarjan(int u){
dfn[u]=low[u]=++idx;
s.push(u),in_stack[u]=1;
for(re int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(!dfn[v]){
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(in_stack[v])low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
int j;
cnt_scc++;
do{
j=s.top();
s.pop();
in_stk[j]=0;
scc[j]=cnt_scc;
}while(j!=u);
}
}