强连通分量
定义:
强连通指的是对于一个有向图,每个点都有路径到另外一个点。
强连通分量则指的是对于一个图,它的极大强连通子图。
tanjan 求法:
对于一个图,考虑他的 dfs 生成树(即为对原图进行 dfs 的一棵树)。
那么对于这棵树,搜索时会出现四种边:
树枝边:搜索到没被访问过的节点,且在树中是当前节点的直接儿子。
前向边:搜索到没被访问的节点,但在树中是当前节点的间接儿子。
横叉边:搜索到已访问到的节点,但不是当前节点的祖先。
返祖边:搜索到已访问的节点,且是当前节点的祖先。
性质: 如果 \(u\) 是一个强连通分量中的第一个访问到的节点,那么其余节点一定是在搜索树中 \(u\) 的子树中。
证明: 反证法即可。
那么现在我们考虑到达 \(u\) 节点的时间戳,即为 \(u\) 是第几个到达的。这里记作 \(dfn[u]\).
在回溯的过程中,维护一个栈,用于存储待处理的节点。
再考虑在以 \(u\) 为根的子树中能到达的最早的在栈中的节点的时间戳。这里记作 \(low[i]\).
那么对于没被访问的节点,有 \(low[u]=\min(low[u],low[v])\)
对于访问过的且在栈中的节点,有 \(low[u]=\min(low[u],dfn[v])\)
其他情况对 \(low[u]\) 均无贡献。
那么当 \(low[u]=dfn[u]\) 时,就表示所有在以 \(u\) 为根的子树中 \(low\) 值等于 \(low[u]\) 的节点组成的图已经满足了极大性和强连通性。
且因为以 \(u\) 为根的子树中 \(low\) 值不等于 \(low[u]\) 的节点已经处理完了,所以这个栈中在 \(u\) 上的节点的 \(low\) 值都等于 \(low[u]\) ,此时他们就组成了一个强连通分量。
代码:
void tarjan(int u){
dfn[u]=low[u]=++dfnnow;
vis[u]=true;
st[++top]=u;
in_st[u]=true;
for(int i=head[u];i;i=edges[i].next){
int v=edges[i].v;
if(!vis[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(in_st[v]){
low[u]=min(low[u],dfn[v]);
}
}
if(dfn[u]==low[u]){
scc++;
while(st[top]!=u){
siz[scc]++;
in_st[st[top]]=false;
col[st[top]]=scc;
top--;
}
siz[scc]++;
in_st[st[top]]=false;
col[st[top]]=scc;
top--;
}
return;
}