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

    圆方树

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

     

  • 相关阅读:
    asp.net大文本保存 framework4.0
    temp文件夹权限对ASP网站以及ASP.NET网站的影响【转载】
    将access数据转移到sql server
    MVC图片上传、剪辑、缩略、水印
    魔兽War3按键精灵Ⅱ(201294)
    PHP中应用CKEditor和CKFinder上传图片读取图片
    C#中get和set个人理解
    asp.net验证码实现
    转三篇文章关于php中session机制
    ORM组件 ELinq (五)映射配置之XML
  • 原文地址:https://www.cnblogs.com/mogeko/p/10809007.html
Copyright © 2011-2022 走看看