Loading docs/graph/scc.md +42 −42 Original line number Diff line number Diff line Loading @@ -25,10 +25,10 @@ Tarjan 发明了很多很有用的东西,下到 NOIP 上到 CTSC 难度的都  有向图的 DFS 生成树主要有 4 种边(不一定全部出现): 1. 树边(tree edge):绿色边,每次搜索找到一个还没有访问过的结点的时候就形成了一条树边。 2. 反祖边(back edge):黄色边,也被叫做回边,即指向祖先结点的边。 3. 横叉边(cross edge):红色边,它主要是在搜索的时候遇到了一个已经访问过的结点,但是这个结点**并不是**当前结点的祖先时形成的。 4. 前向边(forward edge):蓝色边,它是在搜索的时候遇到子树中的结点的时候形成的。 1\. 树边(tree edge):绿色边,每次搜索找到一个还没有访问过的结点的时候就形成了一条树边。 2\. 反祖边(back edge):黄色边,也被叫做回边,即指向祖先结点的边。 3\. 横叉边(cross edge):红色边,它主要是在搜索的时候遇到了一个已经访问过的结点,但是这个结点 **并不是** 当前结点的祖先时形成的。 4\. 前向边(forward edge):蓝色边,它是在搜索的时候遇到子树中的结点的时候形成的。 一个结点的子树内结点的 DFN 都大于该结点的 DFN。 Loading @@ -43,8 +43,8 @@ Tarjan 发明了很多很有用的东西,下到 NOIP 上到 CTSC 难度的都 ### Tarjan 算法求强连通分量 在 Tarjan 算法中为每个结点 u 维护了以下几个变量: 1. $DFN[u]$:深度优先搜索遍历时结点 u 被搜索的次序。 2. $low[u]$:设以 u 为根的子树为 $Subtree(u)$。$low[u]$ 定义为以下结点的 $DFN$ 的最小值:$Subtree(u)$ 中的结点;从 $Subtree(u)$ 通过一条不在搜索树上的边能到达的结点。 1\. $DFN[u]$ :深度优先搜索遍历时结点 u 被搜索的次序。 2\. $low[u]$ :设以 u 为根的子树为 $Subtree(u)$ 。 $low[u]$ 定义为以下结点的 $DFN$ 的最小值: $Subtree(u)$ 中的结点;从 $Subtree(u)$ 通过一条不在搜索树上的边能到达的结点。 显然,按照 DFS 搜索树的递归顺序, $low[u]$ 是单调递增的。 Loading @@ -56,7 +56,6 @@ Tarjan 发明了很多很有用的东西,下到 NOIP 上到 CTSC 难度的都 将上述算法写成伪代码: ``` TARJAN_SEARCH(int u) vis[u]=true low[u]=dfn[u]=++dfncnt Loading @@ -67,7 +66,6 @@ TARJAN_SEARCH(int u) low[u]=min(low[u],low[v])// 回溯 else if v has been in the stack then low[u]=min(low[u],dfn[v]) ``` 对于一个连通分量图,我们很容易想到,在该连通图中有且仅有一个 $DFN[u]=low[u]$ 。该结点一定是在深度遍历的过程中,该连通分量中第一个被访问过的结点,因为它的 DFN 值和 $low$ 值最小,不会被该连通分量中的其他结点所影响。 Loading @@ -83,8 +81,10 @@ void tarjan(int u){ low[u] = dfn[u] = ++dfncnt, s[++tp] = u; for (int i = h[u]; i; i = e[i].nex) { const int &v = e[i].t; if(!dfn[v])tarjan(v), low[u]=min(low[u],low[v]); else if(!scc[v]) low[u]=min(low[u],dfn[v]); if (!dfn[v]) tarjan(v), low[u] = min(low[u], low[v]); else if (!scc[v]) low[u] = min(low[u], dfn[v]); } if (dfn[u] == low[u]) { ++sc; Loading Loading
docs/graph/scc.md +42 −42 Original line number Diff line number Diff line Loading @@ -25,10 +25,10 @@ Tarjan 发明了很多很有用的东西,下到 NOIP 上到 CTSC 难度的都  有向图的 DFS 生成树主要有 4 种边(不一定全部出现): 1. 树边(tree edge):绿色边,每次搜索找到一个还没有访问过的结点的时候就形成了一条树边。 2. 反祖边(back edge):黄色边,也被叫做回边,即指向祖先结点的边。 3. 横叉边(cross edge):红色边,它主要是在搜索的时候遇到了一个已经访问过的结点,但是这个结点**并不是**当前结点的祖先时形成的。 4. 前向边(forward edge):蓝色边,它是在搜索的时候遇到子树中的结点的时候形成的。 1\. 树边(tree edge):绿色边,每次搜索找到一个还没有访问过的结点的时候就形成了一条树边。 2\. 反祖边(back edge):黄色边,也被叫做回边,即指向祖先结点的边。 3\. 横叉边(cross edge):红色边,它主要是在搜索的时候遇到了一个已经访问过的结点,但是这个结点 **并不是** 当前结点的祖先时形成的。 4\. 前向边(forward edge):蓝色边,它是在搜索的时候遇到子树中的结点的时候形成的。 一个结点的子树内结点的 DFN 都大于该结点的 DFN。 Loading @@ -43,8 +43,8 @@ Tarjan 发明了很多很有用的东西,下到 NOIP 上到 CTSC 难度的都 ### Tarjan 算法求强连通分量 在 Tarjan 算法中为每个结点 u 维护了以下几个变量: 1. $DFN[u]$:深度优先搜索遍历时结点 u 被搜索的次序。 2. $low[u]$:设以 u 为根的子树为 $Subtree(u)$。$low[u]$ 定义为以下结点的 $DFN$ 的最小值:$Subtree(u)$ 中的结点;从 $Subtree(u)$ 通过一条不在搜索树上的边能到达的结点。 1\. $DFN[u]$ :深度优先搜索遍历时结点 u 被搜索的次序。 2\. $low[u]$ :设以 u 为根的子树为 $Subtree(u)$ 。 $low[u]$ 定义为以下结点的 $DFN$ 的最小值: $Subtree(u)$ 中的结点;从 $Subtree(u)$ 通过一条不在搜索树上的边能到达的结点。 显然,按照 DFS 搜索树的递归顺序, $low[u]$ 是单调递增的。 Loading @@ -56,7 +56,6 @@ Tarjan 发明了很多很有用的东西,下到 NOIP 上到 CTSC 难度的都 将上述算法写成伪代码: ``` TARJAN_SEARCH(int u) vis[u]=true low[u]=dfn[u]=++dfncnt Loading @@ -67,7 +66,6 @@ TARJAN_SEARCH(int u) low[u]=min(low[u],low[v])// 回溯 else if v has been in the stack then low[u]=min(low[u],dfn[v]) ``` 对于一个连通分量图,我们很容易想到,在该连通图中有且仅有一个 $DFN[u]=low[u]$ 。该结点一定是在深度遍历的过程中,该连通分量中第一个被访问过的结点,因为它的 DFN 值和 $low$ 值最小,不会被该连通分量中的其他结点所影响。 Loading @@ -83,8 +81,10 @@ void tarjan(int u){ low[u] = dfn[u] = ++dfncnt, s[++tp] = u; for (int i = h[u]; i; i = e[i].nex) { const int &v = e[i].t; if(!dfn[v])tarjan(v), low[u]=min(low[u],low[v]); else if(!scc[v]) low[u]=min(low[u],dfn[v]); if (!dfn[v]) tarjan(v), low[u] = min(low[u], low[v]); else if (!scc[v]) low[u] = min(low[u], dfn[v]); } if (dfn[u] == low[u]) { ++sc; Loading