poj2186

题目链接

http://poj.org/problem?id=2186

题目大意

N个点 (1 <= N <= 10,000), M条边 (1 <= M <= 50,000),(A B)表示点A指向点B,输出有多少个点是任意点可以指向的

样例

输入

3 3
1 2
2 1
2 3

输出

1

思路

强连通分量
邻接表
答案需考虑特例:缩点后依旧存在多个强连通分量
这题数据范围m给小了应该是m<=500000

法一

1.时间戳
2.进栈
3.深搜
4.回溯
4.1.low修改
4.2时间戳=父节点时间戳时弹栈到到当前节点

法二

1.正向建边深搜,退栈时打时间戳,使得时间戳最大的点是没有入度的点
2.反向建边,从时间戳大的点开始深搜,搜索到的点是一个强连通分量

法一弱智代码

#include<cstdio>
#include<cstring>
const int nn=10005;
const int mm=500005;
struct rec{int a,b,h;}original[mm];
int n,m,h[nn],a,b,low[nn],dfn[nn],flag[nn],time,sl,st[nn];
int num,sum[nn],now[nn],ans,send[nn],f;
int min(int x,int y){return x<y ?x:y;}
int dfs(int k){
    dfn[k]=low[k]=++time;
    st[++sl]=k; flag[k]=1;
    for(int x_h=h[k]; x_h!=-1; x_h=original[x_h].h){
        int y=original[x_h].b;
        if(flag[y]!=0)low[k]=min(low[k],low[y]);
        if(dfn[y]==0)low[k]=min(low[k],dfs(y));
    }
    if(dfn[k]==low[k]){
            ++num;
            int summ=0;
            do{
                ++summ;
                flag[st[sl]]=0;
                now[st[sl--]]=num;
            }while(st[sl+1]!=k);
            sum[num]=summ;
    }
    return(low[k]);
}
int main(){
    //freopen("poj2186.out","w",stdout);
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof(h));
    for(int i=1; i<=m; ++i){
        scanf("%d%d",&a,&b);
        original[i].a=a;
        original[i].b=b;
        original[i].h=h[a];
        h[a]=i;
    }
    for(int i=1; i<=n; ++i)low[i]=nn;
    time=0; sl=0; 
    memset(dfn,0,sizeof(dfn));
    memset(flag,0,sizeof(flag));
    for(int i=1; i<=n; ++i)if(dfn[i]==0)dfs(i);
    memset(send,0,sizeof(send));
    for(int i=1; i<=m; ++i)
        if(now[original[i].a]!=now[original[i].b])
            ++send[now[original[i].a]];
    ans=0;
    f=0;
    for(int i=1; i<=num; ++i){
        if(send[i]==0){
            if(f==1){
                ans=0;
                break;
            }else{
                ans=sum[i];
                f=1;
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/QF-Home/p/9452701.html