• Tarjan学习笔记


    TARJAN

    前置知识


    • 强连通:对于两点u, v,从u到v和从v到u都有路径,则称u和v强连通
    • 强联通图:强连通图(Strongly Connected Graph)是指在有向图G中,如果对于任意两点u, v,从u到v和从v到u都有路径,则称G是强连通图
    • 极大强连通子图:一个有向图中无法再加一个点的强连通子图
    • 强连通分量(scc):一个有向图的极大强连通子图
    • 领接表存图
    • dfs

    解决的问题


    求图的强连通分量
    通过缩点将一个有向图转化为一个DAG

    思想


    通过dfs的过程中搜索顺序和边的特点进行判断点之间的关系(时间戳)

    过程


    1.一个栈
    将dfs搜到的点存在一个栈中

    2.两个数组
    dfn[ ], low[ ]的定义:
    dfn[u]为 dfs 此有向图过程中搜到u的顺序(时间戳) ,  
    low[u] 为u在当前栈中能到的点中dfn最小值

    3.四种dfs搜索边
    树枝边:指向dfs搜索树中还未遍历到的点的边(指向子树中未遍历过得节点)
    前向边:指向子树中遍历过得节点的边
    后向边:指向栈中祖先的边
    横插边:其他边

    4.DFS

    5.判断low[now]的值
    若当前搜到一条树枝边,则用指向点的low值更新
    若当前搜到一条前向或后向边,则用指向点的dfn值更新//祖先的low未算好且对答案无影响
    若当前搜到一条横插边,则忽略(无影响)

    6.求scc(scccnt,sccnum[ ])
    若当前节点now的dfn和low值相同,则now为当前求的scc中最深度最小的点(栈中now后的点为scc中的点)
    将栈中now节点之后的点出栈,标记为一个scc

    7.CODE

    int dfn[MAXN], low[MAXN], ind;
    int s[MAXN], stp;//栈
    int scccnt, sccnum[MAXN], sccsize[MAXN];
    
    void tarjan(int now) {
        dfn[now] = low[now] = ++ind;
        s[stp++] = now;//stp存的是栈顶的上一个位置 
        
        for (int i = head[now]; i ; i = nxt[i]) {
            
            if(!dfn[v[i]]) {
                tarjan(v[i]);
                low[now] = min(low[v[i]], low[now]);//树枝边 
            }
            else if(!sccnum[v[i]]) {
                low[now] = min(dfn[v[i]], low[now]);//前后向边 
            }
            
        } 
        
        
        if(dfn[now] == low[now]) { // 分割点 
            scccnt++;
            while(s[stp] != now) {
                sccnum[s[--stp]] = scccnt;
                sccsize[scccnt]++; 
            } 
        }
        
        return;
    }
    

    常见模型

    1. [HAOI2006]受欢迎的牛
    //题意为求一个图中唯一的出度为0的强联通 
    
    #include <bits/stdc++.h>
    using namespace std;
    const int MAXN = 100001;
    const int MAXM = 50001;
    int n, m;
    
    int head[MAXN], cnt, nxt[MAXM], v[MAXM];
    void add(int x, int y) {
        nxt[++cnt] = head[x];
        head[x] = cnt;
        v[cnt] = y;
    }
    
    int dfn[MAXN], low[MAXN], ind;//dfn[u] : dfs图过程中搜到u的顺序 ,low[u] : u和u可到的子树中dfn的最小值 
    int s[MAXN], stp;//栈
    int scccnt, sccnum[MAXN], sccsize[MAXN];
    
    int out[MAXN];//每个强联通的出度 
    int ans = 0;
    
    void tarjan(int now) {
        dfn[now] = low[now] = ++ind;
        s[stp++] = now;//stp存的是栈顶的上一个位置 
        
        for (int i = head[now]; i ; i = nxt[i]) {
            
            if(!dfn[v[i]]) {
                tarjan(v[i]);
                low[now] = min(low[v[i]], low[now]);//树枝边 
            }
            else if(!sccnum[v[i]]) {
                low[now] = min(dfn[v[i]], low[now]);//前后向边 
            }
            
        } 
        
        
        if(dfn[now] == low[now]) { // 分割点 
            scccnt++;
            while(s[stp] != now) {
                sccnum[s[--stp]] = scccnt;
                sccsize[scccnt]++; 
            } 
        }
        
        return;
    }
    
    int main() {
        
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= m; i++) {
            int x, y;
            scanf("%d%d", &x, &y);
            add(x, y);
        }
        
        for (int i = 1; i <= n; i++) {
            if(!dfn[i]) tarjan(i);
        }
        //for (int i = 1; i <= n; i++) printf("dfn[%d] = %d, low[%d] = %d, sccnum[%d] = %d
    ", i, dfn[i], i, low[i], i, sccnum[i]);
        
        //求唯一的出度为0的强联通 
        for (int i = 1; i <= n; i++) {
            for (int j = head[i]; j ; j = nxt[j]) {
                if(sccnum[i] != sccnum[v[j]])
                    out[sccnum[i]]++;
            }
        }
        bool flag = 0;
        for (int i = 1; i <= scccnt; i++) {
            if(flag == 1 && out[i] == 0) {
                printf("0");
                return 0;
            }
            else if(out[i] == 0) {
                ans = sccsize[i];
                flag = 1;
            }
        }
        
        
        printf("%d", ans);
        
        
        return 0;
    }
    
    1. [USACO06JAN]牛的舞会The Cow Prom
    //题意为求点数大于1的scc的个数 
    #include <bits/stdc++.h>
    using namespace std;
    
    const int MAXN = 10001;
    const int MAXM = 50001;
    int n, m;
    
    int head[MAXN], cnt, nxt[MAXM], v[MAXM];
    
    void add(int x, int y) {
        nxt[++cnt] = head[x];
        head[x] = cnt;
        v[cnt] = y;
    }
    
    int s[MAXN], stp;
    int dfn[MAXN], low[MAXN], ind;
    int sccnum[MAXN], scccnt, sccsize[MAXN];
    
    void tarjan(int now) {
        dfn[now] = low[now] = ++ind;
        s[stp++] = now;
        
        for (int i = head[now]; i ; i = nxt[i]) {
            if(!dfn[v[i]]) {
                tarjan(v[i]);
                low[now] = min(low[now], low[v[i]]);
            }
            else if(!sccnum[v[i]]) {
                low[now] = min(low[now], dfn[v[i]]);
            }
        }
        
        if(dfn[now] == low[now]) {
            scccnt++;
            while(s[stp] != now) {
                sccnum[s[--stp]] = scccnt;
                sccsize[scccnt]++;
            }
        }
    }
    
    int main() {
        
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= m; i++) {
            int x, y;
            scanf("%d%d", &x, &y);
            add(x, y);
        }
        
        
        for (int i = 1; i <= n; i++)
            if(!dfn[i]) 
                tarjan(i);
        
        int ans = 0;
        for (int i = 1; i <= scccnt; i++)
            if(sccsize[i] > 1)
                ans++;
        
        printf("%d", ans);
        
        return 0;
    }
    
  • 相关阅读:
    html5 返回当前地理位置的坐标点(经纬度)
    C#趣味程序---百鸡百钱
    Android开发:怎样隐藏自己的app应用
    Android Studio一些简单设置
    集群通信组件tribes之集群的消息接收通道
    Java基础实例
    如何用webbrowser获取ajax动态生成的网页的源码?
    phantomjs 下拉滚动条获取网页的全部源码
    Nodejs+定时截图+发送邮件
    关注网页的更新状况,了解最新的handsup 消息.
  • 原文地址:https://www.cnblogs.com/hangzz/p/12384916.html
Copyright © 2020-2023  润新知