poj2186 Popular Cows(强连通分量)(korasaju||tarjan模板题)

转载请注明:http://blog.csdn.net/idrandom/article/details/52064804
题目链接:http://poj.org/problem?id=2186
题意很简单,给你一个有向图,问其中有几个点可以由任意点所到达
求强连通分量然后缩点,找到出度为零的强连通分量,如果出度为零的强连通分量大于一个,则答案不存在,否则答案为该强连通分量中点的个数。
先用Korasaju算法做了一遍,这个比较好理解,正着dfs一遍标记结束时间,然后根据结束时间从后到前对反图做一遍dfs求连通块,求得的连通块即为一个强连通分量。

#include <stdio.h>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cmath>
using namespace std;
const int Max=10050;
struct edge{
    int to,nxt;
}e1[Max*5],e2[Max*5];
int h1[Max],h2[Max];
int siz1,siz2;
int vis1[Max],vis2[Max];
int be[Max],cnt[Max],out[Max];
int st[Max],stz;
int tot;
struct node{
    int u,v;
}e[Max*5];
void init(){
    memset(e1,-1,sizeof(e1));
    memset(e2,-1,sizeof(e2));
    memset(h1,-1,sizeof(h1));
    memset(h2,-1,sizeof(h2));
    siz1=siz2=stz=tot=0;
    memset(vis1,0,sizeof(vis1));
    memset(vis2,0,sizeof(vis2));
    memset(be,0,sizeof(be));
    memset(cnt,0,sizeof(cnt));
    memset(out,0,sizeof(out));

}

void add(int u,int v){
    e1[++siz1].nxt=h1[u];e1[siz1].to=v;h1[u]=siz1;
    e2[++siz2].nxt=h2[v];e2[siz2].to=u;h2[v]=siz2;
}
void dfs1(int rt){
    vis1[rt]=1;
    for(int i=h1[rt];~i;i=e1[i].nxt){
        int to=e1[i].to;
        if(!vis1[to])dfs1(to);
    }
    st[++stz]=rt;
}
void dfs2(int rt){
    vis2[rt]=1;
    cnt[tot]++;
    for(int i=h2[rt];~i;i=e2[i].nxt){
        int to=e2[i].to;
        if(!vis2[to])dfs2(to);
    }
    be[rt]=tot;
}
int main(void)
{
    int n,m;
    scanf("%d%d",&n,&m);
    init();
    for(int i=1;i<=m;i++){
        scanf("%d%d",&e[i].u,&e[i].v);
        add(e[i].u,e[i].v);
    }
    for(int i=1;i<=n;i++){
        if(!vis1[i])dfs1(i);
    }
    for(int i=stz;i>=1;i--){
        if(!vis2[st[i]]){
            ++tot;
            dfs2(st[i]);
        }
    }
    for(int i=1;i<=m;i++){
        if(be[e[i].u]!=be[e[i].v])out[be[e[i].u]]++;
    }
    int ans=0;
    int flag=0;
    for(int i=1;i<=tot;i++){
        if(out[i]==0){
            if(ans){flag=-1;break;}
            else {
                ans++;
                flag=i;
            }
        }
    }
    if(flag==-1)puts("0");
    else printf("%d\n",cnt[flag]);
    return 0;
}

接下来用tarjan做一遍,tarjan的优势在于无需建立反图,只需一遍dfs,比较快,根据dfn和low的值就可以求出强连通分量了。
学习资料[转]:请参考https://www.byvoid.com/blog/scc-tarjan/
下面贴代码:

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
using namespace std;
struct node{
    int to,nxt;
}e[50050];
struct edge{
    int u,v;
}ei[50050];
int h[10010];
int myst[10010];
int vis[10010];
int belong[10010];
int cnt[10010];
int out[10010];
int dfn[10010],low[10010];
int st,siz,cn,idx;
void init(){
    memset(h,-1,sizeof h);
    memset(e,-1,sizeof e);
    memset(vis,0,sizeof vis);
    memset(belong,0,sizeof belong);
    memset(cnt,0,sizeof cnt);
    memset(out,0,sizeof out);
    st=siz=cn=idx=0;
}
void add(int u,int v){
    e[++siz].to=v;
    e[siz].nxt=h[u];
    h[u]=siz;
}
void tarjan(int u){
    dfn[u]=low[u]=++idx;
    vis[u]++;
    myst[++st]=u;
    for(int i=h[u];~i;i=e[i].nxt){
        int v=e[i].to;
        if(!vis[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(vis[v]==1){
            low[u]=min(low[u],dfn[v]);
        }
    }
    int v;
    if(dfn[u]==low[u]){
        ++cn;
        do{
            v=myst[st--];
            belong[v]=cn;
            vis[v]++;
            cnt[cn]++;
        }while(u!=v);
    }
}
int main(){
    int n,m;
    init();
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&ei[i].u,&ei[i].v);
        add(ei[i].u,ei[i].v);
    }
    for(int i=1;i<=n;i++)if(!vis[i])tarjan(i);
    for(int i=1;i<=m;i++){
        if(belong[ei[i].u]!=belong[ei[i].v]){
            out[belong[ei[i].u]]++;
        }
    }
    int flag=0;
    int ans=0;
    for(int i=1;i<=cn;i++){
        if(out[i]==0){
            if(flag){ans=0;break;}
            else {flag++;ans=cnt[i];}
        }
    }
    printf("%d\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值