• Tarjan 强连通分量学习笔记


    在一个有向图中,如果某两点间都有互相到达的路径,那么称中两个点强连通,如果任意两点都强连通,那么称这个图为强连通图;一个有向图的极大强连通子图(不被原图其它强连通子图包含)称为强连通分量

    Tarjan 算法可以在 $O(n + m)$ 的时间内求出一个图的所有强连通分量。

    若将有向图的强连通分量都视作一个点,则原图会形成有向无环图(DAG)。

    定义

    栈,DFS 树中同一棵子树中的点在栈中是相邻的。

    $mathrm{dfn}(u)$ 表示进入节点 $u$ 时的时间(时间截)。

    $mathrm{low}(u)$ 表示由节点 $u$ 开始搜索所能到达的点中,在搜索树上是 $u$ 的祖先且 $mathrm{dfn}$ 最小的节点的 $mathrm{dfn}$。

    描述

    1. 选定一个节点作为根节点,开始 DFS;

    2. 初始化当前点的 $mathrm{dfn}$ 和 $mathrm{low}$ 均为当前时间截,并进栈;

    3. 遍历当前点 $v$ 的所有邻接点( $v$ 出边连的点);

    4. 如果某个邻接点 $u$ 在栈中,更新 $mathrm{low}(v) = min(mathrm{low}(v), mathrm{dfn}(u))$;

    5. 如果某个邻接点 $u$ 不在栈中,则对 $u$ 进行 DFS,完成后更新 $mathrm{low}(v) = min(mathrm{low}(v), mathrm{low}(u))$;

    6. $v$ 所有邻接点都完成 DFS 后,如果满足 $mathrm{low}(v) = mathrm{dfn}(v)$,则将栈中从 $v$ 到栈顶的所有元素出栈,并标记为一个强连通分量。

    解释

    如果某个邻接点 $u$ 在栈中,更新 $mathrm{low}(v) = min(mathrm{low}(v), mathrm{dfn}(u))$;

    $u$ 已被访问过且还未出栈,说明 $v$ 找到它的祖先 $u$,并形成了一个环,此时要用 $u$ 去更新 $v$ 的最远祖先。

    如果某个邻接点 $u$ 不在栈中,则对 $u$ 进行 DFS,完成后更新 $mathrm{low}(v) = min(mathrm{low}(v), mathrm{low}(u))$;

    点 $u$ 出发能到达的最远祖先,点 $v$ 一定也能到达。

    $v$ 所有邻接点都完成 DFS 后,如果满足 $mathrm{low}(v) = mathrm{dfn}(v)$,则将栈中从 $v$ 到栈顶的所有元素出栈,并标记为一个强连通分量。

    如果当前点 $v$ 为根的子树下,无论怎么走也无法到达 $v$ 的祖先,说明整棵子树下的点组成一个强连通分量。因为从 $v$ 进栈后,所有进栈的点都是子树内的点,所以这个点在栈中的位置到栈顶位置中的点为同一个强连通分量。

    模板

    #include <cstdio>
    #include <cstring>
    
    const int SIZE = 100005;
    
    int scc[SIZE], sccTot;
    int st[SIZE], top;
    int low[SIZE], dfn[SIZE], time;
    int h[SIZE], to[SIZE << 1], nxt[SIZE << 1], tot;
    
    int min(int x, int y) {
    	return x < y ? x : y;
    }
    
    void add(int x, int y) {
    	to[++tot] = y;
    	nxt[tot] = h[x];
    	h[x] = tot;
    }
    
    void tarjan(int x) {
    	low[x] = dfn[x] = ++time;
    	st[++top] = x;
    	for (int i = h[x]; i; i = nxt[i]) {
    		int y = to[i];
    		if (!dfn[y]) {
    			tarjan(y);
    			low[x] = min(low[x], low[y]);
    		} else if (!scc[y]) {
    			low[x] = min(low[x], dfn[y]);
    		}
    	}
    
    	if (low[x] == dfn[x]) {
    		scc[x] = ++sccTot;
    		while (st[top] != x) {
    			scc[st[top--]] = sccTot;
    		}
    		top--;
            // x 出栈
    	}
    }
    
    int main() {
    	int n, m;
    	scanf("%d %d", &n, &m);
    	for (int i = 1; i <= m; i++) {
    		int x, y;
    		scanf("%d %d", &x, &y);
    		add(x, y);
    	}
    	for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
    
    	printf("%d
    ", sccTot);
    	for (int i = 1; i <= n; i++) printf("%d ", scc[i]);
    	return 0;
    }
    

    参考资料

  • 相关阅读:
    Beta冲刺——day2
    Beta冲刺——day1
    OpenGL立方体在世界坐标系中_缩放_旋转_平移_顶点片源着色器_光照作用_棋盘纹理贴图
    FIRST集和FOLLOW集
    现代计算机接口实验 (五)0809实验
    现代计算机接口实验 (四)0832实验
    现代计算机接口实验 (二)8253实验
    现代计算机接口实验 (三)8255实验
    现代计算机接口实验 (一)熟悉环境
    可编程控制器实训
  • 原文地址:https://www.cnblogs.com/lcfsih/p/14391385.html
Copyright © 2020-2023  润新知