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;
    }
    
  • 相关阅读:
    怎么查看京东店铺的品牌ID
    PPT编辑的时候很卡,放映的时候不卡,咋回事?
    codevs 1702素数判定2
    codevs 2530大质数
    codevs 1488GangGang的烦恼
    codevs 2851 菜菜买气球
    hdu 5653 Bomber Man wants to bomb an Array
    poj 3661 Running
    poj 1651 Multiplication Puzzle
    hdu 2476 String Painter
  • 原文地址:https://www.cnblogs.com/Nicoppa/p/11492055.html
Copyright © 2011-2022 走看看