zoukankan      html  css  js  c++  java
  • Tarjan求强连通分量 缩点

    强连通分量的定义:

      在一张有向图中,如果两个点u,v之间能相互到达则称这两个点u,v是强连通的,在这个基础上如果有向图G中的任意两个顶点都强连通,那么称图G是一个强连通图。有向强连通图的极大强连通子图称为强连通分量。极大强连通子图就是强连通子图中最大的那个,它不被其他强连通子图所包括。

      概念挺多,特别混乱的感觉。理一下...

      一个强连通图中的每一对顶点都必须强连通。

      一个强连通图不叫做强连通分量,只叫做强连通图。

      一个强连通子图G若为强连通分量那么必然是一个最大的强连通子图,也就是原图中不存在另一个图G',使得G是G'的真子集。

      举个例子:

      

      在图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达,子图{1,3,4}是一个强连通子图并不是强连通分量,因为它被子图{1,2,3,4}所包含,所以只是一个强连通子图。

    强连通分量的应用:

       缩点:在一个有向图中将所有的强连通分量都缩成一个点的话,原图就会变成一个DAG(有向无环图),因为DAG有一些比较好的性质,所以会给解题带来很大的方便。

       举个例子:

        原图:

      

      找出图中所有的强连通分量:

      

      缩完点之后:

      

    Tarjan 算法:

      Tarjan算法以dfs的方式实现,每个强连通分量为搜索树中的一颗子树,搜索的时候,把当前搜索树中为处理的结点加入一个栈,回溯时可以判断到栈中的结点是否构成一个强连通分量。

       这个书面语不懂也罢,接着往下看吧。

       先介绍一下搜索时会遇到的四种边吧:

       树枝边:dfs时经过的边。

       前向边:与dfs方向一致,由某个结点指向其子孙的边。

       后向边:与dfs方向相反,有某个结点指向其祖先的边。

       横叉边:由某个结点指向搜索树种另一子树的边。

       dfn[u]: 表示结点u的搜索次序编号,也就是时间戳.

       low[u]: 表示结点u或u的子树能够回溯到的最早的栈中结点的时间戳(dfn)。

      如果(u,v)为树枝边,u为v的父结点:low[u]=min(low[u],low[v]);

      如果(u,v)为后向边或者指向栈中结点的横叉边:low[u]=min(low[u],dfn[v]);

      指向栈中的结点的横叉边的原因是:一个点只能属于一个强连通分量,如果不是指向栈中结点的横叉边,那么横叉边的另一结点v一定在此之前已经属于了另一个强连通分量。

      当结点u的搜索过程结束之后,如果dfn[u]=low[u],那么以u为根的搜索子树上所有还在栈中的结点(即u和栈中在u之后的结点)是一个强连通分量,即可推栈。

      因为当dfn[u]=low[u]时表示u即u的子孙结点最早能够到达的点便是结点u,那么u就是它的子孙中的最高祖先。

      算法演示见博客:https://www.cnblogs.com/five20/p/7594239.html

    例题:

      #10091. 「一本通 3.5 例 1」受欢迎的牛: https://loj.ac/problem/10091

       解题思路:

      由题意可得,一头牛u若为受欢迎的牛,那么他必然受到其他所有牛的喜欢,由于喜欢可以传递,那么意味着从其他的任一头牛出发都能到达牛u,也可得到一个环上的牛都是互相喜欢的,

    所以把每个强连通子图找出来,缩点,然后整个图就变成了DAG,又因为一头牛要受到其他所有牛的喜欢,那么它不能喜欢除了自己这个联通块外的牛(图中已经不存在环了),所以这时只需要

    统计一下出度为0的牛的个数,或者直接建反向边,改为统计入度为0的牛的个数。

      最后注意缩点后连通块数目多于一个的情况,此时因为联通快多于1个且互不连通,导致没有牛可以收到其他所有牛的喜欢了。

    #include<bits/stdc++.h>
    using namespace std;
    #define INF 0x3f3f3f3f
    #define ll long long
    #define maxn 50009
    inline ll read()
    {
        ll x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')    f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ll)(ch-'0');ch=getchar();}
        return x*f;
    }
    int head[maxn],belong[maxn],low[maxn],dfn[maxn],in[maxn];
    struct edge
    {
        int to,nxt;
    }p[maxn];
    bool vis[maxn];
    stack<int> s;
    int n,m,k,cnt,tot,now,ans,id,sum;
    
    void add(int x,int y)
    {
        ++cnt,p[cnt].to=y,p[cnt].nxt=head[x],head[x]=cnt;
    }
    
    void Tarjan(int u)
    {
        dfn[u]=++id;
        low[u]=dfn[u];
        s.push(u);
        vis[u]=1;
        for(int i=head[u];i;i=p[i].nxt)
        {
            int v=p[i].to;
            if(!dfn[v])
            {
                Tarjan(v);
                low[u]=min(low[u],low[v]);
            }
            else if(!belong[v])
            {
                low[u]=min(low[u],dfn[v]);
            }
        }
        if(dfn[u]==low[u])
        {
            tot++;
            while(1)
            { 
                int v=s.top();
                belong[v]=tot;   
                s.pop();
                vis[v]=0;
                if(u==v)
                    break; 
            }
        }
    }
    int main()
    {
    //    freopen(".in","r",stdin);
    //    freopen(".out","w",stdout);
        n=read(),m=read();
        for(int i=1;i<=m;i++)
        {
            int x=read(),y=read();
            add(y,x);//建反边,可以将统计连通块的出度转化为统计连通块的入度 
        }
        
        for(int i=1;i<=n;i++)
        {
            if(!dfn[i])
                Tarjan(i);
        }
        for(int i=1;i<=n;i++)
            for(int j=head[i];j;j=p[j].nxt)
            {
                int v=p[j].to;
                if(belong[i]!=belong[v])
                    in[belong[v]]++;
            }
        for(int i=1;i<=tot;i++)
            if(in[i]==0)
            {
                now=i;
                sum++;
            }
        if(sum!=1)
            puts("0");
        else
        {
            sum=0;
            for(int i=1;i<=n;i++)
                if(belong[i]==now)
                    sum++;
            printf("%d
    ",sum);    
        }
    //    fclose(stdin);
    //    fclose(stdout);
        return 0;  
    }
    /* 
    6 8
    1 2
    1 3
    2 4
    3 4
    3 5
    4 1
    4 6
    5 6
    */
    之前的Tarjan模板
    #include<bits/stdc++.h>
    using namespace std;
    #define re register int
    #define ll long long
    #define INF 0x3f3f3f3f
    #define maxn 10009
    #define maxm  50009
    inline ll read()
    {
        ll x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ll)(ch-'0');ch=getchar();}
        return x*f;
    }
    int head[maxn],s[maxn],belong[maxn],dfn[maxn],low[maxn],in[maxn];
    int n,m,k,ans,tot,cnt,sum,id,top,block;
    struct edge
    {
        int to,nxt;
    }p[maxm];
    
    void add(int x,int y)
    {
        p[++cnt]={y,head[x]},head[x]=cnt;
    }
    
    void Tarjan(int u)
    {
        dfn[u]=low[u]=++id;
        s[++top]=u;
        for(int i=head[u];i;i=p[i].nxt)
        {
            int v=p[i].to;
            if(!dfn[v])
            {
                Tarjan(v);
                low[u]=min(low[u],low[v]);
            }
            else if(!belong[v])
                low[u]=min(low[u],dfn[v]);
        }
        if(dfn[u]==low[u])
        {
            ++block;
            while(1)
            {
                belong[s[top]]=block;
                if(s[top]==u)
                    break;
                --top;
            }
            --top;
        }
    }
    int main()
    {
    //    freopen(".in","r",stdin);
    //    freopen(".out","w",stdout);
        n=read(),m=read();
        for(int i=1;i<=m;i++)
        {
            int x=read(),y=read();
            add(y,x);
        }
        for(int i=1;i<=n;i++)
            if(!dfn[i])
                Tarjan(i);
    /*    for(int i=1;i<=n;i++)
            cout<<belong[i]<<" ";
        cout<<endl;*/
        for(int u=1;u<=n;u++)
            for(int i=head[u];i;i=p[i].nxt)
            {
                int v=p[i].to;
                if(belong[u]!=belong[v])
                    in[belong[v]]++;
            }
        int now;
        for(int i=1;i<=block;i++)
            if(in[i]==0)
                ans++,now=i;
        if(ans!=1)
        {
            puts("0");
            return 0;
        }
        ans=0;
        for(int i=1;i<=n;i++)
            if(belong[i]==now)
                ++ans;
        printf("%d
    ",ans);    
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    现在的模板

       

  • 相关阅读:
    mysql常见的hint
    SQL优化:一篇文章说清楚Oracle Hint的正确使用姿势
    Oracle中常见的Hint(一)
    oracle中hint 详解
    Oracle hint 详解
    neo4j简单学习
    Maven的pom.xml文件结构之基本配置parent和继承结构
    使用TASM编译COFF格式和连接
    使用双引擎,让kbmmw 的客户端访问更方便(既给浏览器做服务,也给桌面程序做服务)
    成大事者不纠结(碰到难办的事情的时候,要就事论事,专注当下,放下过去,不忧未来,也不要记仇。防范之举要节制。是做事情的其中一种策略,而且还要分场合)
  • 原文地址:https://www.cnblogs.com/Dxy0310/p/9783909.html
Copyright © 2011-2022 走看看