zoukankan      html  css  js  c++  java
  • scc(强连通分量)【tarjon||kosaraju】一点感悟

    强连通这个地方看了三天 灵机一动 写一篇博客巩固巩固

    首先 tarjan 这个东西貌似是读 [ˈtɑːrjæn](他er见)【也有知乎大佬说好像读[ˈtɑːryæn],按照前面内个读8】

    tarjan陪伴强联通分量

    生成树完成后思路才闪光

    欧拉跑过的七桥古塘

    让你 心驰神往

        ——《膜你抄》

    Menci大佬tql %%% 舔舔舔

    下面开始心驰神往:

    scc(strongly connected components) 有向图强连通分量

    DAG ( Directed Acyclic Graph) 有向无环图

    为啥要提DAG

    因为本菜鸡发现有的题会让你用

    Tarjan算法缩点然后再求DAG最长路(DP或spfa求最长路)

    也就是Tarjan缩点+DAG最长路||Tarjan缩点+SPFA求DAG上单源最短路

    为啥要用缩点

    众所周知,有向无环图(DAG)总是有着一些蜜汁优越性,因为没有环,你可以放心的在上面跑dfs,搞DP,但如果是一张有向有环图,事情就会变得尴尬起来了

    思考一下会发现如果不打vis标记就会t飞(一直在环里绕啊绕),但是如果打了,又不一定能保证最优解

    而你一看题目却发现显然根据一些贪心的原则,这个环上每个点的最大贡献都是整个环的总贡献

    这个时候缩点就显得很有必要了,因为单个点的贡献和整个环相同,为什么不去把整个环缩成一个超级点呢?

    将有向有环图 通过Tarjan缩点转换为DAG 再搞一搞岂不是美滋滋

    比较好的博文

    初探tarjan算法  https://www.luogu.org/blog/styx-ferryman/chu-tan-tarjan-suan-fa-qiu-qiang-lian-tong-fen-liang-post

    Tarjan算法:详解与心得  https://www.cnblogs.com/yanyiming10243247/p/9294160.html

    图的割点与割边  https://www.cnblogs.com/WWHHTT/p/9745499.html

    Tarjan算法:求解图的割点与桥(割边)  https://www.cnblogs.com/nullzx/p/7968110.html

    写这个东西其实就是想把这篇代码贴在这(写的太好了QAQ)然后再给它加点注释

    洛谷 P3387(tarjan缩点+求最长路)

    注意:

    Tarjan+SPFA求DAG上单源最短路模板题

    用Tarjan在原图上求SCC 缩点

    用缩点之后的SCC建一个有向无环图

    SCC权为此SCC内所有点点权和

    在新建的DAG上将SCC权视为边权跑SPFA

    求SCC[1]到SCC[n]的最长路即为所求答案

    原文链接https://blog.csdn.net/yiqzq/article/details/80587578 

    /*
    有向图求缩点
    */
    
    #include <bits/stdc++.h>
    using namespace std;
    const int maxn = 1e4 + 5;
    int n, m;
    struct node {
        int nxt, v, u;
    } e[maxn * 10];
    int head[maxn], Index, dfn[maxn], low[maxn], scc, top;
    int belog[maxn], tot, inStack[maxn], Stack[maxn], in[maxn];
    //int num[maxn];//各个强连通分量包含点的个数
    int value[maxn], vis[maxn], dis[maxn];
    vector<int>G[maxn];//临接表存缩点生成的图
    int ret[maxn];
    void init() {
        memset(vis, 0, sizeof(vis));//spfa中判断是否已经在队列中
        memset(dis, 0, sizeof(dis));//用于求距离
        memset(value, 0, sizeof(value));//保存点权
        memset(head, -1, sizeof(head));//用于前向星
        memset(belog, 0, sizeof(belog));//判断原来的顶点i属于缩点后的的哪个顶点
        memset(dfn, 0, sizeof(dfn));//时间戳
        memset(low, 0, sizeof(low));//判断顶点所能返回的最早的节点
        memset(in, 0, sizeof(in));//判断入度
        memset(inStack, 0, sizeof(inStack));//判断是否在栈中
    //    memset(num, 0, sizeof(num));
        memset(ret, 0, sizeof(ret));//缩点后点权的总值
        Index = 0;//时间戳
        tot = 0;//前向星
        top = 0;//模拟栈
        scc = 0;//联通分量个数
    }
    void add_edge(int u, int v) {//加边操作 链式前向星存图
        e[tot].u = u;
        e[tot].v = v;
        e[tot].nxt = head[u];
        head[u] = tot++;
    }
    void tarjan(int u) {
        low[u] = dfn[u] = ++Index;//初始化
        inStack[u] = 1;//表明u节点已经在栈中
        Stack[top++] = u;//将u入栈
        for(int i = head[u]; ~i; i = e[i].nxt) {
            int v = e[i].v;
            if(!dfn[v]) {
                tarjan(v);//继续dfs        u是起点 v是终点↓
                low[u] = min(low[u], low[v]);//u能到达的最小次序号是min(它自己能到达的最小次序号,连接点v能到达的最小次序号)
            } else if(inStack[v]) {
                low[u] = min(low[u], dfn[v]);//如果v在栈内,u能到达的最小次序号是min(它自己能到达的最小次序号,v的次序号)
            }
        }
        if(low[u] == dfn[u]) {//如果相等,就说明找到了一个强连通分量
            scc++;
            int v;
            do {
                v = Stack[--top];
                inStack[v] = 0;
                belog[v] = scc;
    //            num[scc]++; //统计各个scc包含点的个数
                ret[scc] += value[v];//此SCC内所有点点权和为新的ssc点边权
            } while(v != u);
        }
    }
    void solve() {
        for(int i = 1; i <= n; i++ ) {
                //为了求出所有的连通分量,如果保证是连通图,那么可以不用循环
            if(!dfn[i])tarjan(i);
        }
    }
    int spfa(int num) {
        queue<int>q;
        q.push(num);
        dis[num] = ret[num];//这个地方注意一下,初始值dis[x]应该为ret[x]
        while(!q.empty()) {
            int u = q.front();
            q.pop();
            vis[u] = 0;
            for(int i = 0; i < G[u].size(); i++ ) {
                int v = G[u][i];
                dis[v] = max(dis[v], dis[u] + ret[v]);//注意这里用spfa跑出来的是最长路
                if(!vis[v]) {
                    q.push(v);
                    vis[v] = 1;
                }
            }
        }
        int ans = 0;
        for(int i = 1; i <= scc; i++) ans = max(ans, dis[i]);
        return ans;
    }
    int main() {
        init();
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++) scanf("%d", &value[i]);
        for(int i = 1; i <= m; i++) {
            int u, v;
            scanf("%d%d", &u, &v);
            add_edge(u, v);
        }
        solve();
        for(int i = 0; i < tot; i++) {//重新构图
            int t1 = belog[e[i].u];
            int t2 = belog[e[i].v];
            if(t1 == t2) continue;
            G[t1].push_back(t2);//临接表存图
            in[t2]++;
        }
        int ans = 0;
        for(int i = 1; i <= scc; i++) {
    
            if(!in[i]) {//如果是最大值,那么是以入度为0的节点开始的
                    //因为这一定是最大的 ,贪心
                memset(vis, 0, sizeof(vis));
                ans = max(ans, spfa(i));
            }
        }
        printf("%d
    ", ans);
        return 0;
    }
    //5 6
    //3 1
    //1 2
    //2 3
    //4 3
    //4 5
    //5 3

    蒟蒻总是更懂你✿✿ヽ(°▽°)ノ✿

    kosaraju这个坑改天再来填8(逃)

    某个大佬关于tarjan-无向图(桥、割点、双联通分量)的笔记

    一.基本概念
      1.桥:对于一个无向图,如果删除某条边后,该图的连通分量增加,则称这条边为桥
      2.割点/割项:对于一个无向图,如果删除某个节点u节点后,该图的连通分量增加,则节点u为割项或关节点
      3.点-双联通:对于一个连通图,如果任意两点至少存在两条点不重复路径,则称这个图为点双连通(也就是通常说的的双联通)
      4.边-双联通:对于一个连通图,如果任意两点至少存在两条边不重复路径,则称该图为边双连通的
    二.判断条件
      1.桥:
        - 存在重边必定不为桥
        -low[v]>dfn[u]
      2.割点/割项:
        -u不为搜索起点,low[v]>=dfn[u]
        -u为搜索起点,size[ch]>=2
      PS: –> 一般情况下,不建议在tarjan中直接输出答案(可能会有重复)
         –> 在有重边的情况下,将tarjan传值中的father改为其编号,由于存边的连续性 只要判断 (当前编号)i != (father编号)pre^1
        { 初始值tarjan(i,-1) 编号:0—-2m }
       *************************************************
      3.点-双联通:大致同求割点
        -用栈保存边(并非点)
        -遇到割点后开始出栈,直到当前的边出栈后
      4.边-双联通:(目前只提供一种方法 其实是不知道另一种方法)
        -第一次dfs找出所有的桥
        -第二次dfs保证不经过桥.一个连通图即为一个边-双联通分量
     三.构造双连通图
       缩点后找度为1的个数,需要加的边为(tot+1)/2
    ---------------------
    原文:https://blog.csdn.net/qq_27121257/article/details/78083791

    点&&边双连通看这篇博文:

    寒假2019培训:双连通分量(点双+边双)  https://blog.csdn.net/hailiang70303/article/details/87553759

    写的比较乱orz

  • 相关阅读:
    052-90
    052-89
    052-88
    052-87
    052-86
    html5的manifest
    js中数字转金钱格式
    CSS复合样式
    资料
    异步
  • 原文地址:https://www.cnblogs.com/guanwen769aaaa/p/11289516.html
Copyright © 2011-2022 走看看