• 图论学习笔记——强连通分量/双连通分量


    预备知识

    • 强连通:对于图((V,E))上的两个顶点(u,v),若存在从(u)(v)的有向路径,同时也存在从(v)(u)的有向路径,(注意有路径即可,也就是允许有中间顶点)则称这两个顶点强连通。
    • 强连通图:若图((V,E))上的任意两个顶点都强连通,则称这个图为强连通图。
    • 强连通分量:有向图的极大强连通子图

    强连通分量(SCC,Strongly Connected Components)

    求强连通分量(Tarjan算法)

    (dfn[i]:=) 顶点(i)在搜索过程中的次序编号(时间戳),即记录顶点(i)是第几个被搜索到的顶点。

    (low[i]:=) 顶点(u)及其后代顶点所能追溯到的最早的顶点(即祖先顶点)(v)的时间戳(dfn[v])。当顶点(u)第一次被搜索到时,初始化为low[i]=dfn[i].

    int dfs_clock, scc_cnt;
    int dfn[maxn], low[maxn], sccno[maxn];
    
    void dfs(int u) {
        dfn[u] = low[u] = ++dfs_clock;
        //给顶点u打上时间戳
        s.push(u);
        for (int i = head[u]; i; i = e[i].next) {
            int v = e[i].to;
            if (!dfn[v]) {
                //顶点v还没被搜索到
                dfs(v);
                low[u] = min(low[u], low[v]);
                //维护祖先中最小的时间戳
            }
            else if (!sccno[v]) {
                low[u] = min(low[u], dfn[v]);
                //顶点v已经被搜索过了,但还不属于某一个SCC
            }
        }
        //对顶点u的所有后代顶点完成搜索之后
        //开始判断顶点u是不是这个强连通分量中第一个出现的顶点
        if (low[u] == dfn[u]) {
            scc_cnt++;
            //SCC数量+1
            while (1) {
                int x = s.top(); s.pop();
                sccno[x] = scc_cnt;
                //给分量中的所有顶点记录所在SCC的编号
                if (x == u) break;
                //访问完u之后,就完成了对这个SCC所有顶点的访问,跳出
            }
        }
    }
    
    void find_scc(int n) {
        dfs_clock = scc_cnt = 0;
        mem(sccno, 0);
        mem(dfn, 0);
        for (int i = 1; i <= n; i++) {
            if (!dfn[i]) dfs(i);
        }
    }
    

    缩点

    顾名思义,将图中的强连通分量看作是一个点,就是缩点。同时原图变为一个DAG,如此便可以将一个有环的图转化为DAG,可以利用DAG的性质解决问题。

    问题一:给定一个有向图((V,E)),包含(n)个点和(m)条边,问至少还要再添加多少条边才能使整个图变成强连通图。

    对于DAG,这个问题的答案是(max(a,b)),其中(a)是入度为零的顶点个数,(b)是出度为零的顶点个数。特别地,如果DAG中只有一个点,则答案为0。但问题中给出的图不一定无环,此时就可以用缩点的方法将图转化为DAG。

    问题二:给定一个有向图((V,E)),包含(n)个点和(m)条边,每个点有一个权值。求一条路径,使得路径上点的权值和最大。允许多次经过一条边或一个点,但权值只计算一次。

    对于DAG,这个问题就是求DAG上的最长路,用DAG上的dp即可解决。定义(dp[i])为从顶点(i)出发的路径的最大权值和,则转移方程为(dp[i]=max(dp[i],dp[j]+val[i])),其中顶点(j)满足:存在(i→j)的有向边。

    需要注意的是,在方程中需要先求出(dp[j]),才能用它来更新(dp[i])。具体代码实现有两种方法:

    1. 记忆化搜索;
    2. 先求图的拓扑排序,再以拓扑排序的倒序进行dp。

    题目给定的图可能有环,只需对原图求强连通分量,缩点,建立新图,则新图就是DAG。

  • 相关阅读:
    Python笔记:高阶函数
    linux C生成UUID的方法
    c语言连接mysql完整演示
    linux下C语言连接mysql数据库演示(在控制台编译的)
    mysql版本问题,导致的mysql.user表下面的字段变成了authentication_string
    选择排序算法
    汉诺塔递归实现
    操作系统复习第一章
    二叉树的基本操作
    字符串的模式匹配算法
  • 原文地址:https://www.cnblogs.com/streamazure/p/13816457.html
Copyright © 2020-2023  润新知