首页 > 其他 > 详细

强连通分量

时间:2019-09-14 17:49:53      阅读:92      评论:0      收藏:0      [点我收藏+]

tarjan

基于深度优先搜索,用于寻找有向图中的连通块。
主要代码如下:

inline void tarjan(int x){
    dfn[x]=low[x]=++idx;
    vis[x]=1;
    sta[++top]=x;
    for(int i=head[x];i;i=e[i].nxt){
        int v=e[i].v;
        if(!dfn[v]){//还未访问过这个节点
            tarjan(v);
            low[x]=min(low[x],low[v]);
        }
        else if(vis[v]==1){//这个节点在栈中
            low[x]=min(low[x],dfn[v]);
        }
    }
    if(dfn[x]==low[x]){//找到一个强联通分量
        ++cnt;
        while(x!=sta[top]){
            vis[sta[top]]=0;
            qlt[sta[top]]=cnt;//染色,标记这个节点属于第几号强联通分量
            siz[cnt]++;//记录这一个连通块的节点数
            top--;
        }
        vis[x]=0;//还要将这个节点弹出来
        qlt[x]=cnt;
        siz[cnt]++;
        top--;
    }
}

如果是要遍历整张图找到所有的连通块,通常还要加这句

for(int i=1;i<=n;i++){
    if(!dfn[i]) tarjan(i);
}

因为图中不一定全部都是联通的。
每一个点只访问了一次,所以tarjan的时间复杂度是O(n)的。
通常还要用到tarjan缩点。缩点的时候将全部边遍历一次,
如果两个节点不属于同一个连通块,那么就连一条边,为了避免这种情况:
a->c b->c a,b属于同一个连通块。
可以用并查集处理一下,避免重复将两个相同的连通块连边。(重复连边好像也并没有什么影响)

题目


USACO 2003 Fall:
popular cow
这道题首先要知道:对于一个有向无环图,出度为零的点,从其他任意一个点都可以到达这个点。
所以我们只需要将原图用tarjan缩点变成一个有向无环图,找到出度为零的点,输出那个连通块的大小就行了。

#include<bits/stdc++.h>
#define cg c=getchar()
#define N 10020
#define M 50050
using namespace std;
template<typename xxx>inline void read(xxx &x){
    x=0;int f=1;char cg;
    while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
    x*=f;
}
template<typename xxx>inline void print(xxx x){
    if(x<0) x=-x,putchar('-');
    if(x>9) print(x/10);
    putchar(x%10+48);
}
int n,m,aa,bb;
struct node{
    int v,nxt;
}e[M];
int head[N];
int tot;
inline void add(int a,int b){
    ++tot;
    e[tot].v=b;
    e[tot].nxt=head[a];
    head[a]=tot;
}
int idx;
int dfn[N];
int low[N];
int sta[N];
int vis[N];
int top;
int cnt;
int qlt[N];
int siz[N];
int cd[N];
inline void tarjan(int x){
    dfn[x]=low[x]=++idx;
    vis[x]=1;
    sta[++top]=x;
    for(int i=head[x];i;i=e[i].nxt){
        int v=e[i].v;
        if(!dfn[v]){
            tarjan(v);
            low[x]=min(low[x],low[v]);
        }
        else if(vis[v]==1){
            low[x]=min(low[x],dfn[v]);
        }
    }
    if(dfn[x]==low[x]){
        ++cnt;
        while(x!=sta[top]){
            vis[sta[top]]=0;
            qlt[sta[top]]=cnt;
            siz[cnt]++;
            top--;
        }
        vis[x]=0;
        qlt[x]=cnt;
        siz[cnt]++;
        top--;
    }
}
int main(){
    read(n);read(m);
    for(int i=1;i<=m;i++){
        read(aa);read(bb);
        add(aa,bb);
    }
    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=e[j].nxt){
            int v=e[j].v;
            if(qlt[i]!=qlt[v]){
                cd[qlt[i]]++;//因为不用重建一张图,只需要记录出度就行了
            }
        }
    }
    //因为重构的图也有可能存在两个连通块之间没有边连接,它们的出度也为零,所以要考虑这种情况
    int ans=0;
    int id;
    for(int i=1;i<=cnt;i++){
        if(cd[i]==0){
            ans++;
            id=i;
        }
    }
    if(ans==1){//只能有一个出度为零的连通块
        print(siz[id]);
    }
    else print(0);
}

IOI 1996 网络协议
这道题有两个问题,第一个问题还是比较明显,就是缩点后看有几个入度为0的点,每一个入度为零的点都必须放消息。
第二问就相当于将缩点后的图变成一个连通块。每一个入度为零的边都要增加一条入度,每一个出度为零的边都要增加一条出度。最后取较大值。
有个小细节,如果缩点后只有一个点,那么就不用加边了,所以要特判一下。
code:

#include<bits/stdc++.h>
#define N 120
#define M 13000
#define cg c=getchar()
using namespace std;
template<typename xxx>inline void read(xxx &x){
    x=0;int f=1;char cg;
    while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
    x*=f;
} 
template<typename xxx>inline void print(xxx x){
    if(x<0) x=-x,putchar('-');
    if(x>9) print(x/10);
    putchar(x%10+48);
}
struct node{
    int u,v,nxt;
}e[M],e2[M];
int head[N];
int tot;
inline void add(int a,int b){
    ++tot;
    e[tot].u=a;
    e[tot].v=b;
    e[tot].nxt=head[a];
    head[a]=tot;
}
int n;
int aa;
int idx;
int top;
int dfn[N];
int low[N];
int vis[N];
int sta[N];
int qlt[N];
int cnt;
inline void tarjan(int x){
    dfn[x]=low[x]=++idx;
    vis[x]=1;
    sta[++top]=x;
    for(int i=head[x];i;i=e[i].nxt){
        int v=e[i].v;
        if(!dfn[v]) {
            tarjan(v);
            low[x]=min(low[x],low[v]);
        }
        else if(vis[v]==1){
            low[x]=min(low[x],dfn[v]);
        }
    }
    if(dfn[x]==low[x]){
        ++cnt;
        while(x!=sta[top]){
            qlt[sta[top]]=cnt;
            vis[sta[top]]=0;
            top--;
        }
        qlt[x]=cnt;
        vis[x]=0;
        top--;
    }
} 
int rd[N];
int cd[N];
int ans;
int main(){
//  freopen("protocols10.in","r",stdin);
    read(n);
    for(int i=1;i<=n;i++){
        while(true){
            read(aa);
            if(aa==0) break;
            add(i,aa);
        }
    }
    for(int i=1;i<=n;i++){
        if(!dfn[i]) tarjan(i);
    }
    for(int i=1;i<=tot;i++){
        int x=e[i].u;
        int y=e[i].v;
        if(qlt[x]!=qlt[y]) {
            rd[qlt[y]]++;
            cd[qlt[x]]++;
        }
    }
    int ans1=0;
    int ans2=0;
    for(int i=1;i<=cnt;i++){
        if(rd[i]==0){
            ans1++;
            ans++;
        }
        if(cd[i]==0){
            ans2++;
        } 
    }
    print(ans);putchar('\n');
    if(cnt==1) print(0);//特判 只有一个连通块,不需要加边。
    else print(max(ans1,ans2));
}

消息的传递:
跟上一题的第一问一样。。。只不过输入变成了邻接矩阵。
code:

#include<bits/stdc++.h>
#define cg c=getchar()
#define N 1200
#define M N*(N-1)/2
using namespace std;
template<typename xxx>inline void read(xxx &x){
    x=0;int f=1;char cg;
    while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
    x*=f;
} 
template<typename xxx>inline void print(xxx x){
    if(x<0) x=-x,putchar('-');
    if(x>9) print(x/10);
    putchar(x%10+48);
}
struct node{
    int u,v,nxt;
}e[M];
int head[N];
int tot;
inline void add(int a,int b){
    ++tot;
    e[tot].u=a;
    e[tot].v=b;
    e[tot].nxt=head[a];
    head[a]=tot;
} 
int n;
int aa;
/*--------------------------*/
int idx;
int dfn[N];
int low[N];
int vis[N];
int sta[N];
int top;
int cnt;
int qlt[N];
inline void tarjan(int x){
    dfn[x]=low[x]=++idx;
    sta[++top]=x;
    vis[x]=1;
    for(int i=head[x];i;i=e[i].nxt){
        int v=e[i].v;
        if(!dfn[v]){
            tarjan(v);
            low[x]=min(low[x],low[v]);
        }
        else if(vis[v]==1){
            low[x]=min(low[x],dfn[v]);
        }
    }
    if(dfn[x]==low[x]){
        ++cnt;
        while(x!=sta[top]){
            qlt[sta[top]]=cnt;
            vis[sta[top]]=0;
            top--;
        }
        qlt[x]=cnt;
        vis[x]=0;
        top--;
    }
}
int rd[N];
int main(){
    read(n);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            read(aa);
            if(aa==1){
                add(i,j);
            }
        }
    }
    for(int i=1;i<=n;i++){
        if(!dfn[i]) tarjan(i);
    }
    for(int i=1;i<=tot;i++){
        int x=e[i].u;
        int y=e[i].v;
        if(qlt[x]!=qlt[y]){
            rd[qlt[y]]++;
        }
    }
    int ans=0;
    for(int i=1;i<=cnt;i++){
        if(rd[i]==0){
            ans++;
        }
    }
    print(ans);
}

心得

  • 有向无环图中一个出度为零的点,从任意一个点出发都可到达它。
  • 有向无环图至少有一个入度为零的点。
  • 有向无环图的生成树个数等于入度非零的节点的入度积。
  • 强联通分量的题好像多数与入度出度有关,想题的时候可以往这方面考虑。

强连通分量

原文:https://www.cnblogs.com/doublety/p/11519454.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!