• Tarjan算法求有向图的强连通分量


    算法描述

    tarjan算法思想:从一个点开始,进行深度优先遍历,同时记录到达该点的时间(dfn记录到达i点的时间),和该点能直接或间接到达的点中的最早的时间(low[i]记录这个值,其中low的初始值等于dfn)。如图:

    极易懂得Tarjan算法(求有向图的强联通分量)

      假设我们从1开始DFS,那么到达1的时间为1,到达2的时间为2,到达3的时间为3。同时,点1能直接或间接到达的点中,最小时间为1,点2能通过3间接到达点1,所以点2可到达最早的点时间为1,点3可以直接到达点1,故点3到达的最早的点的时间为1。)。对于每一个没有被遍历到的点A,如果从当前点有一条到未遍历点A的有向边,则遍历到A,同时将点A入栈,时间戳+1并用dfn[a]记录到达点A的时间,枚举从A发出的每一条边,如果该边指向的点没有被访问过,那么继续dfs,回溯后low[a]=min(low[a],low[j])(其中j为A可以到达的点。)如果该点已经访问过并且该点仍在栈里,那么low[a]=min(low[a],dfn[j])。

    解释:

      若点j没有被访问过,那么回溯后low[j]就是j能到达最早点,a能到达的最早点当然就是a本身能到达的最早点,或者a通过j间接到达的最早点。若点j已经被访问过,那么low[j]必然没有被回溯所更新。所以low[a]就等于a目前能到达的最小点或a直接到达点j时点j的访问时间。注意:两个句子中的“或”其实指的是两者的最小值。

    那么如果我们回溯到一个点K他的low[k]=dfn[k]那么我们将K及其以前在栈中的点依次弹出,这些点即为一个强连通分量。(说明从k出发又回到k)

    证明:

      因为该点dfn=low,所以在栈中的该点以上的点都能由该点直接或间接的到达。同时栈中在该点前的任意一点j,其dfn[j] != low[j](否则点j比点k靠前,又因为dfn[j]=low[j],j一定先被弹出了。)那么这个点j通过low[j]这个时间的点,一定能到达点k,否则,low[j]能到达点i,又因为dfn>=low所以有2种情况1、dfn>low:那么我们可以找到前面一个更小的点。2、dfn=low:应该在回溯到i的时候就找到了一个强连通分量,从而出栈了。而点k前的点没有出栈,证明其中任意一点都能直接或者间接到达点k,进而证明这些点可以两两互达。

    先用简单模板刷一道水题入门:

    迷宫城堡

    本题的边不带权值,可以用vector表示邻接链表:

    #include <cstdio>
    #include <memory.h>
    #include <vector>
    #include <algorithm>
    
    using namespace std;
    
    //本题的顶点号从1到n,故可直接用作vector的下标,不需要用head数组离散化 
    
    const int MAXN = 10001;
    
    int top;
    int Stack[MAXN];
    bool inStack[MAXN];
    //DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号  
    int dfn[MAXN], low[MAXN];
    int Bcnt, Dindex; //记录强连通的个数和当前时间 
    vector<int> ve[MAXN]; //邻接表保存边
    
    void init() {
        Bcnt = Dindex = top = 0;
        memset(dfn, -1, sizeof dfn);
        memset(inStack, false, sizeof inStack);
        for (int i = 1; i < MAXN; ++i) ve[i].clear();
    }
    
    void tarjan(int u) {
        int v = 0;
        dfn[u] = low[u] = ++Dindex;
        inStack[u] = true;
        Stack[++top] = u;
        int t = ve[u].size();
        for (int i = 0; i < t; ++i) {
            v = ve[u][i];
            if (dfn[v] == -1) {
                tarjan(v);
                low[u] = min(low[u], low[v]);
            } else if (inStack[v])
                low[u] = min(low[u], dfn[v]);
        }
        if (dfn[u] == low[u]) {
            Bcnt++;
            do {
                v = Stack[top--];
                inStack[v] = false;
            } while (u != v);
        }
    } 
    
    int main() {
    #ifndef ONLINE_JUDGE
        freopen("in.txt", "r", stdin);
        freopen("out.txt", "w", stdout);
    #endif
        int n, m;
        while (scanf("%d%d", &n, &m) != EOF) {
            if (m == 0 && n == 0) break;
            init();
            while (m--) {
                int a, b;
                scanf("%d%d", &a, &b);
                ve[a].push_back(b); 
            }
            for (int i = 1; i <= n; ++i) 
                if (dfn[i] == -1) tarjan(i);
            if (Bcnt == 1) puts("Yes");
            else puts("No");
        } 
    
        return 0;
    }
    View Code

    Summer Holiday

    只需找出所有的强连通分量,然后遍历所有的边,对于边u->v,若u、v在不同的连通子图,显然v的子图可以从边u->v达到,对于这样的两个强连通分量,可以视为一个子图,只需打电话给u所所在的子图中人,然后让这个人再联系u,v所在的两个强连通分量中的所有人。故只需找出所有的子图,打给每个子图中话费最低的那个人即可。

    #include <cstdio>
    #include <memory.h>
    #include <algorithm> 
    
    const int MAXN = 1001;
    struct N {
        int u, v;//u to v
        int next;//下一个顶点 
    } edge[MAXN * 2]; //m <= 2000
    int head[MAXN], edgenum;
    
    int DFN[MAXN], Low[MAXN], Dindex;//到达某顶点的费用,该子图最小费用 ,当前费用 
    int Bcnt, Belong[MAXN];    //强连通分量的个数, 当前顶点所属的连通分量 
    int Stack[MAXN], top;
    bool inStack[MAXN];
    
    
    void addedge(int u, int v) {
        N e = {u, v, head[u]};
        edge[edgenum] = e;
        head[u] = edgenum++; 
    }
    
    void init() {
        edgenum = top = Bcnt = Dindex =  0;
        memset(head, -1, sizeof head);
        memset(DFN, -1, sizeof DFN);
        memset(inStack, false, sizeof inStack);
    }
    
    void tanjar(int u) {
        int v = 0;
        DFN[u] = Low[u] = ++Dindex;
        Stack[++top] = u;
        inStack[u] = true;
        for (int i = head[u]; i != -1; i = edge[i].next) {
            v = edge[i].v;
            if (DFN[v] == -1) {
                tanjar(v);
                Low[u] = std::min(Low[u], Low[v]);
            } else if (inStack[v]) 
                Low[u] = std::min(Low[u], DFN[v]);
        }
        if (DFN[u] == Low[u]) {
            ++Bcnt;
            do {
                v = Stack[top--];
                Belong[v] = Bcnt; //将点加入该强连通图 
                //printf("bcnt = %d v = %d
    ", Bcnt, v); 
                inStack[v] = false;
            } while (u != v);
        }
    }
    
    int main() {
    #ifndef ONLINE_JUDGE
        freopen("in.txt", "r", stdin);
        freopen("out.txt", "w", stdout);
    #endif
        int n, m;
        int w[MAXN] = {0};
        while (scanf("%d%d", &n, &m) != EOF) {
            init();
            for (int i = 1; i <= n; ++i)
                scanf("%d", &w[i]);
            while (m--) {
                int a, b;
                scanf("%d%d", &a, &b);
                addedge(a, b);
            }
            for (int i = 1; i <= n; ++i) {
                if (DFN[i] == -1) tanjar(i);
            }
            //合并强连通图 
            int mfee[MAXN] = {0};//记录下每个强连图集的最小话费 
            bool inode[MAXN] = {false}; //不可达的连通图 
            int pos = 0;
            for (int i = 0; i < edgenum; ++i) {
                int u = edge[i].u;
                int v = edge[i].v;
                if (Belong[u] != Belong[v])  //v所在的连通子图可以由u到达 
                    inode[Belong[v]] = true;
            }
            for (int i = 1; i <= Bcnt; ++i) {
                if (!inode[i]) pos++;
                mfee[i] = 1e9;
            }
            for (int i = 1; i <= n; ++i) {
                int t = Belong[i];
                if (!inode[t]) 
                    mfee[t] = std::min(mfee[t], w[i]);
            }
            int sum = 0;
            for (int i = 1; i <= Bcnt; ++i) {
                if (mfee[i] != 1e9) sum += mfee[i];
            }
            printf("%d %d
    ", pos, sum);
        } 
    
        return 0;
    }
    View Code

    Instantaneous Transference

    题意:让矿车采到尽可能多的矿。#表示墙,数字表示矿的数目,*表示定点传送,可选择传或者不传,矿车只能向右和向下开,不能倒退。

    先来看下SPFA算法,可以求带环最短(最长路径):Currency Exchange

    宝藏

    #include <cstdio>
    #include <memory.h>
    #include <vector>
    
    using namespace std;
    
    const int MAXN = 100001;
    
    inline int Min(int a, int b) {
        return a < b ? a : b;
    }
    
    inline int Max(int a, int b) {
        return a > b ? a : b;
    }
    
    struct arc {
        int u, v, next;
    } edge[150001];
    int head[MAXN];
    int edgenum;
    
    int DFN[MAXN], Low[MAXN], Dindex;
    int Stack[MAXN], top;
    bool inStack[MAXN];
    int  Belong[MAXN], Bnum[MAXN], Bcnt;
    
    void addedge(int u, int v) {
        arc na = {u, v, head[u]};
        edge[edgenum] = na;
        head[u] = edgenum++;
    }
    
    void init() {
        Bcnt = top = Dindex = edgenum = 0; 
        memset(head, -1, sizeof head);
        memset(DFN, 0, sizeof DFN);
        memset(inStack, false, sizeof inStack);
        memset(Bnum, 0, sizeof Bnum);
    }
    
    void tarjan(int u) {
        int v = 0;
        DFN[u] = Low[u] = ++Dindex;
        Stack[++top] = u;
        inStack[u] = true;
        for (int i = head[u]; i != -1; i = edge[i].next) {
            v = edge[i].v;
            if (DFN[v] == 0) {
                tarjan(v);
                Low[u] = Min(Low[u], Low[v]);
            } else if (inStack[v]) 
                Low[u] = Min(Low[u], DFN[v]);
        }
        if (DFN[u] == Low[u]) {
            Bcnt++;
            do {
                v = Stack[top--];
                inStack[v] = false;
                Belong[v] = Bcnt;
                Bnum[Bcnt]++;
            } while (u != v);
        }
    }
    
    vector<int> G[MAXN]; 
    bool vis[MAXN]; 
    int dfs(int s) {
        int maxnum = 0;
        vis[s] = true;
        int size = G[s].size();
        for (int i = 0; i < size; ++i) {
            int v = G[s][i];
            if (!vis[v]) {
                int tmp = dfs(v);
                maxnum = Max(maxnum, tmp);
                vis[v] = false;
            }
        }
        return Bnum[s] + maxnum;
    }
    
    int main() {
    #ifndef ONLINE_JUDGE
        freopen("in.txt", "r", stdin);
        freopen("out.txt", "w", stdout);
    #endif
        int n, m, s;
        int caseid = 0;
        while (scanf("%d%d%d", &n, &m, &s) != EOF) {
            init();
            while (m--) {
                int u, v;
                scanf("%d%d", &u, &v);
                addedge(u, v);
            }
            tarjan(s);
            for (int i = 1; i <= Bcnt; ++i) G[i].clear();
            for (int i = 0; i < edgenum; ++i) {
                if (DFN[edge[i].u] == 0) continue;
                int u = Belong[edge[i].u];
                int v = Belong[edge[i].v];
                if (u != v) G[u].push_back(v);
            }
            memset(vis, false, sizeof vis);
            printf("Case %d:
    %d
    ", ++caseid, dfs(Belong[s]));
        }
        return 0;
    }
    View Code
  • 相关阅读:
    使用SQL查询所有数据库名和表名
    vue打包时给静态资源增加版本号
    mac笔记本好用的快捷键汇总
    jquery项目好用的插件汇总
    通过js禁止输入空格(试用场景:当用字符串拼接插入dom节点时,onkeyup这些方法都不好使可用这个)
    textarea和type=number输入去空格限制字数问题
    用websocket建立远程连接(vue)
    配置本地服务器
    webpack打包路径问题
    序列号和反序列化==》nodejs之querystring模块(尼玛,太强大,好用耶)
  • 原文地址:https://www.cnblogs.com/fripside/p/3587782.html
Copyright © 2020-2023  润新知