zoukankan      html  css  js  c++  java
  • 『Tarjan』Tarjan求强连通分量模板

    学习Tarjan前提须知

    Tarjan是一个能够求强连通分量的算法。何为强联通?就是在一个图中,两点可以相互到达从而形成的一个环,我们称这个环为强联通,其中,在这个图中所能组成点最多的环,我们称它为强连通分量,而我们的Tarjan就能求强联通与强联通分量 甚至能进行缩点等一系列操作

    算法内容

    竞赛需要用到的点

    1、Tarjan求出强联通后自由度很高,建议不要和强连通分量绑在一起

    2、Tarjan较为常见,考虑可以组成一套模型来使用

    Tarjan求强联通分量略讲

    本人对Tarjan的low算法理解不深,也没有哪个博客作出具体的解释,这里就引用wiki了

    Tarjan算法 wiki

    Tarjan 算法

    Robert E. Tarjan (1948~) 美国人。

    Tarjan 发明了很多算法结构。光 Tarjan 算法就有很多,比如求各种连通分量的 Tarjan 算法,求 LCA(Lowest Common Ancestor,最近公共祖先)的 Tarjan 算法。并查集、Splay、Toptree 也是 Tarjan 发明的。

    我们这里要介绍的是在有向图中求强连通分量的 Tarjan 算法。

    另外,Tarjan 的名字 j 不发音,中文译为塔扬。

    DFS 生成树

    在介绍该算法之前,先来了解 DFS 生成树 ,我们以下面的有向图为例:

    有向图的 DFS 生成树主要有 4 种边(不一定全部出现):

    1. 树边(tree edge):绿色边,每次搜索找到一个还没有访问过的结点的时候就形成了一条树边。
    2. 反祖边(back edge):黄色边,也被叫做回边,即指向祖先结点的边。
    3. 横叉边(cross edge):红色边,它主要是在搜索的时候遇到了一个已经访问过的结点,但是这个结点 并不是 当前结点的祖先时形成的。
    4. 前向边(forward edge):蓝色边,它是在搜索的时候遇到子树中的结点的时候形成的。

    我们考虑 DFS 生成树与强连通分量之间的关系。

    如果结点 (u) 是某个强连通分量在搜索树中遇到的第一个结点,那么这个强连通分量的其余结点肯定是在搜索树中以 (u) 为根的子树中。(u) 被称为这个强连通分量的根。

    反证法:假设有个结点 (v) 在该强连通分量中但是不在以 (u) 为根的子树中,那么 (v)(u) 的路径中肯定有一条离开子树的边。但是这样的边只可能是横叉边或者反祖边,然而这两条边都要求指向的结点已经被访问过了,这就和 (u) 是第一个访问的结点矛盾了。得证。

    Tarjan 算法求强连通分量

    在 Tarjan 算法中为每个结点 (u) 维护了以下几个变量:

    1. (dfn[n]) :深度优先搜索遍历时结点 u 被搜索的次序。
    2. (low[u]) :设以 u 为根的子树为 (Subtree(u))(low[u]) 定义为以下结点的 (dfn) 的最小值: (Subtree(u)) 中的结点;从 (Subtree(u)) 通过一条不在搜索树上的边能到达的结点。

    一个结点的子树内结点的 dfn 都大于该结点的 dfn。

    从根开始的一条路径上的 dfn 严格递增,low 严格非降。

    按照深度优先搜索算法搜索的次序对图中所有的结点进行搜索。在搜索过程中,对于结点 和与其相邻的结点 (v) (v 不是 u 的父节点)考虑 3 种情况:

    1. (v) 未被访问:继续对 v 进行深度搜索。在回溯过程中,用 (low[v]) 更新 (low[u]) 。因为存在从 (u)(v) 的直接路径,所以 (v) 能够回溯到的已经在栈中的结点,(u) 也一定能够回溯到。
    2. (v) 被访问过,已经在栈中:即已经被访问过,根据 (low) 值的定义(能够回溯到的最早的已经在栈中的结点),则用 (dfn[v]) 更新 (low[u])
    3. (v) 被访问过,已不在在栈中:说明 (v) 已搜索完毕,其所在连通分量已被处理,所以不用对其做操作。

    对于一个连通分量图,我们很容易想到,在该连通图中有且仅有一个 (dfn[u] = low[u]) 。该结点一定是在深度遍历的过程中,该连通分量中第一个被访问过的结点,因为它的 DFN 值和 LOW 值最小,不会被该连通分量中的其他结点所影响。

    因此,在回溯的过程中,判定 (dfn[u] = low[u]) 的条件是否成立,如果成立,则栈中从 (u) 后面的结点构成一个 SCC。

    实现代码如下 参考LuoGuP2341强连通分量模板

    [此代码未编译 可能会有问题 请斟酌参考]

    //#define fre yes
    
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    
    const int N = 50005;
    int low[N], dfn[N];
    int head[N << 1], to[N << 1], ver[N << 1];
    int color[N], Stack[N], de[N];
    bool Vis[N];
    
    int tot;
    void addedge(int x, int y) {
        ver[tot] = y;
        to[tot] = head[x];
        head[x] = tot++;
    }
    
    int num, top, col;
    void tarjan(int x) {
        dfn[x] = low[x] = ++num;
        Stack[++top] = x;
        Vis[x] = 1;
        for (int i = head[x]; ~i; i = to[i]) {
            int v = ver[i];
            if(!dfn[v]) {
                tarjan(v);
                low[x] = std::min(low[x], low[v]);
            } else if(Vis[v]) {
                low[x] = std::min(low[x], dfn[v]);
            }
        }
        
        if(dfn[x] == low[x]) {
            ++col;
            color[x] = col;
            Vis[x] = 0;
            while(Stack[top] != x) {
                color[Stack[top]] = col;
                Vis[Stack[top--]] = 0;
            } top--;
        }
    }
    
    int main() {
        memset(head, -1, sizeof(head));
        static int n, m;
        scanf("%d %d", &n, &m);
        for (int i = 1; i <= m; i++) {
            int u, v;
            scanf("%d %d", &u, &v);
            addedge(u, v);
        }
        
        for (int i = 1; i <= n; i++) {
            if(!dfn[i]) {
    			tarjan(i);
            }
        }
        
        for (int i = 1; i <= n; i++) {
            for (int j = head[i]; ~j; j = to[j]) {
                int v = ver[j];
                if(color[i] != color[v]) {
                    de[color[i]]++;
                }
            }
        }
    
        int ans = 0, u = 0, k = 0;
        for (int i = 1; i <= col; i++) {
            if(!de[i]) {
                u++;
                k = i;
            }
        }
    
        if(u == 1) {
            for (int i = 1; i <= n; i++) {
                if(color[i] == k) ans++;
            } printf("%d
    ", ans);
        } else puts("0");
        return 0;
    }
    
  • 相关阅读:
    线程池小结(一)
    [转]ViewPager学习笔记(一)——懒加载
    [转]Private Libraries、Referenced Libraries、Dependency Libraries的区别
    关于context你必须知道的一切
    【转】在mac上配置安卓SDK
    【转】HTTP中的长连接和短连接分析
    中间件解析FDMEMTABLE.delta生成SQL的方法
    delphi 中配置文件的使用(*.ini)和TIniFile 用法
    Delphi 字符串加密与解密函数
    Delphi编写的等长加密与解密
  • 原文地址:https://www.cnblogs.com/Nicoppa/p/11492055.html
Copyright © 2011-2022 走看看