zoukankan      html  css  js  c++  java
  • Tarjan:强连通分量 割点

     (他羊)

    Tarjan是一种dfs算法。

    有向图的强联通分量

    如果一个有向图的子图中,任意两点可以相互到达,那么这就组成了一个强联通分量。

    在Tarjan算法中,需要维护一个栈stk,

    每个节点有两个值:dfn[],即dfs序;low[],表示这个节点最多经过一条横叉边能到达的dfn最小的点的dfn。

    流程:

    dfs到当前的节点为u,枚举u能到达的点v。

    若v没有被dfs过,递归dfs,并用low[v]更新low[u];

    若v被更新过且在栈中,则说明u->v是一条横叉边,并用dfn[v]更新low[u]。

    遍历完u的子节点后,若dfn[u] == low[u],则说明构成了一个强联通分量。将栈中u及以后的节点都染成相同的颜色并弹出。

    • 各个强联通分量出栈的顺序是缩点形成的图中拓扑序的逆序。

    几道模板题:

    Luogu P3387 【模板】缩点 缩点+记忆化搜索

    #include<cstdio>
    #include<iostream>
    #include<cmath>
    #include<cstring>
    #define MogeKo qwq
    using namespace std;
    const int maxn = 2e5+10;
    int n,m,ans;
    int cnt,num,now,top;
    int a[maxn],x[maxn],y[maxn],val[maxn],f[maxn];
    int dfn[maxn],low[maxn],sta[maxn],col[maxn];
    int head[maxn],to[maxn],nxt[maxn];
    bool insta[maxn];
    
    void add(int x,int y) {
        to[++cnt] = y;
        nxt[cnt] = head[x];
        head[x] = cnt;
    }
    
    void clear() {
        memset(head,0,sizeof(head));
        memset(to,0,sizeof(to));
        memset(nxt,0,sizeof(nxt));
        cnt = 0;
    }
    
    void tarjan(int u) {
        dfn[u] = low[u] = ++now;
        sta[++top] = u;
        insta[u] = true;
        for(int i = head[u]; i; i = nxt[i]) {
            int v = to[i];
            if(!dfn[v]) {
                tarjan(v);
                low[u] = min(low[u],low[v]);
            } else if(insta[v])
                low[u] = min(low[u],dfn[v]);
        }
        if(low[u] == dfn[u]) {
            col[u] = ++num;
            insta[u] = false;
            val[num] += a[u];
            while(sta[top] != u) {
                int v = sta[top--];
                col[v] = num;
                insta[v] = false;
                val[num] += a[v];
            }
            top--;
        }
    }
    
    void dfs(int u) {
        if(f[u]) return;
        f[u] = val[u];
        int sum = 0;
        for(int i = head[u]; i; i = nxt[i]) {
            int v = to[i];
            dfs(v);
            sum = max(sum,f[v]);
        }
        f[u] += sum;
    }
    
    int main() {
        scanf("%d%d",&n,&m);
        for(int i = 1; i <= n; i++)
            scanf("%d",&a[i]);
        for(int i = 1; i <= m; i++) {
            scanf("%d%d",&x[i],&y[i]);
            add(x[i],y[i]);
        }
        for(int i = 1; i <= n; i++)
            if(!dfn[i]) tarjan(i);
        clear();
        for(int i = 1; i <= m; i++)
            if(col[x[i]] != col[y[i]])
                add(col[x[i]],col[y[i]]);
        for(int i = 1; i <= num; i++) {
            dfs(i);
            ans = max(ans,f[i]);
        }
        printf("%d",ans);
        return 0;
    }
    View Code

    Luogu P2341 [HAOI2006]受欢迎的牛 

    #include<cstdio>
    #include<iostream>
    #define MogeKo qwq
    using namespace std;
    
    const int maxn = 100005;
    int n,m,a,b,sum,ans;
    int now,top,num;
    int dfn[maxn],low[maxn],sta[maxn],out[maxn],col[maxn];
    int cnt,to[maxn],nxt[maxn],head[maxn];
    bool insta[maxn];
    
    void add(int x,int y) {
        to[++cnt] = y;
        nxt[cnt] = head[x];
        head[x] = cnt;
    }
    
    void Tarjan(int u) {
        dfn[u] = low[u] = ++now;
        sta[++top] = u;
        insta[u] = true;
        for(int i = head[u]; i; i = nxt[i]) {
            int v = to[i];
            if(!dfn[v]) {
                Tarjan(v);
                low[u] = min(low[u],low[v]);
            } else if(insta[v])
                low[u] = min(low[u],dfn[v]);
        }
        if(low[u] == dfn[u]) {
            col[u] = ++num;
            insta[u] = false;
            while(sta[top]!=u) {
                int v = sta[top];
                col[v] = num;
                insta[v] = false;
                top--;
            }
            top--;
        }
    }
    
    int main() {
        scanf("%d%d",&n,&m);
        for(int i = 1; i <= m; i++) {
            scanf("%d%d",&a,&b);
            add(a,b);
        }
        for(int i = 1; i <= n; i++)
            if(!dfn[i])
                Tarjan(i);
        for(int u = 1; u <= n; u++)
            for(int i = head[u]; i; i = nxt[i]) {
                int v = to[i];
                if(col[u] != col[v])
                    out[col[u]]++;
            }
        for(int i = 1; i <= num; i++) {
            if(!out[i]) {
                if(ans) {
                    printf("0");
                    return 0;
                }
                for(int u = 1; u <= n; u++)
                    if(col[u] == i)ans++;
            }
        }
        printf("%d",ans);
        return 0;
    }
    View Code

    无向图的强连通分量

    割点:删去后原图不连通的点。

    割边(桥):删去后原图不连通的边。

    点双连通分量:无割点的极大强连通子图。

    边双连通分量:无割边的极大强连通子图。

    • 有割点不一定有桥,有桥一定存在割点。
    • 桥一定是割点依附的边。
    • 一个点可以在最多2个点双里,但只能在1个边双里(否则可以合并)。

    求割点/割边:

    根结点u为割顶当且仅当它有两个或者多个子结点; 
    非根结点u为割顶当且仅当u存在结点v,使得v极其所有后代都没有反向边可以连回u的祖先(不包括u),即low[v]>=dfn[u]。

    桥的求法其实也是类似的,当结点u的子结点v的后代通过反向边只能连回v,那么删除这条边(u, v)就可以使得图非连通了。即low[v]>dfn[u]。

    Luogu P3388 【模板】割点(割顶)

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<cmath>
    #define MogeKo qwq
    using namespace std;
    const int maxn = 1e6+10;
    const int INF = 2147483647;
    int n,m,cnt,now,num,top,sum;
    int x,y,z;
    int to[maxn],head[maxn],nxt[maxn];
    int dfn[maxn],low[maxn];
    bool iscut[maxn];
    
    void add(int x,int y) {
        to[++cnt] = y;
        nxt[cnt] = head[x];
        head[x] = cnt;
    }
    
    void Tarjan(int u,int fa) {
        dfn[u] = low[u] = ++now;
        int ch = 0;
        for(int i = head[u]; i; i = nxt[i]) {
            int v = to[i];
            if(!dfn[v]) {
                if(fa == u)ch++;
                Tarjan(v,fa);
                low[u] = min(low[u],low[v]);
                if(fa != u && low[v] >= dfn[u]) iscut[u] = true;
            }
            low[u] = min(low[u],dfn[v]);
            if(fa == u && ch >= 2) iscut[u] = true;
        }
    }
    
    int main() {
        scanf("%d%d",&n,&m);
        for(int i = 1; i <= m; i++) {
            scanf("%d%d",&x,&y);
            add(x,y);
            add(y,x);
        }
        for(int i = 1; i <= n; i++)
            if(!dfn[i])
                Tarjan(i,i);
        for(int i = 1; i <= n; i++)
            if(iscut[i])sum++;
        printf("%d
    ",sum);
        for(int i = 1; i <= n; i++)
            if(iscut[i]) printf("%d ",i);
        return 0;
    }
    View Code

    割边:

    void tarjan(int u,int fa) {
        dfn[u] = low[u] = ++now;
        sta[++top] = u;
        insta[u] = true;
        for(int i = head[u]; i; i = nxt[i]) {
            int v = to[i];
            if(v == fa) continue;
            if(!dfn[v]) {
                tarjan(v,u);
                low[u] = min(low[u],low[v]);
                if(low[v] > dfn[u])
                    cut[i] = true;
            } else if(insta[v])
                low[u] = min(low[u],dfn[v]);
        }
        if(low[u] == dfn[u]) {
            int v;
            num++;
            do {
                v = sta[top--];
                insta[v] = false;
                col[v] = num;
            } while(v != u);
        }
    }
    View Code

    圆方树

    将每个点双作为一个方点,这个点双中的点作为圆点,去掉点双中所有内部的边,并将圆点连接到对应的方点。

     

  • 相关阅读:
    django页面分类和继承
    django前端从数据库获取请求参数
    pycharm配置django工程
    django 应用各个py文件代码
    CF. 1428G2. Lucky Numbers(背包DP 二进制优化 贪心)
    HDU. 6566. The Hanged Man(树形背包DP DFS序 重链剖分)
    小米邀请赛 决赛. B. Rikka with Maximum Segment Sum(分治 决策单调性)
    区间树 学习笔记
    CF GYM. 102861M. Machine Gun(主席树)
    2016-2017 ACM-ICPC East Central North America Regional Contest (ECNA 2016) (B, D, G, H)
  • 原文地址:https://www.cnblogs.com/mogeko/p/10809007.html
Copyright © 2011-2022 走看看