POJ 2186 Popular Cows 强连通分量 kosaraju

Popular Cows
Time Limit: 2000MS      Memory Limit: 65536K
Total Submissions: 31544        Accepted: 12825

Description
Every cow's dream is to become the most popular cow in the herd. In a herd of N (1 <= N <= 10,000) cows, you are given up to M (1 <= M <= 50,000) ordered pairs of the form (A, B) that tell you that cow A thinks that cow B is popular. Since popularity is transitive, if A thinks B is popular and B thinks C is popular, then A will also think that C is
popular, even if this is not explicitly specified by an ordered pair in the input. Your task is to compute the number of cows that are considered popular by every other cow.

Input
* Line 1: Two space-separated integers, N and M

* Lines 2..1+M: Two space-separated numbers A and B, meaning that A thinks B is popular.

Output
* Line 1: A single integer that is the number of cows who are considered popular by every other cow.

Sample Input

3 3
1 2
2 1
2 3

Sample Output

1

Hint
Cow 3 is the only cow of high popularity.

Source
USACO 2003 Fall

显然 如果从牛a有一条路到牛b 牛a便崇拜牛b
这题可以通过构建反向图 从牛i出发 如果可以到达其余N-1头牛 便是被所有牛崇拜
这样复杂度为O(n*m) 据说也可以过

但是为了装b
显然 图中的强连通分量中任意2头牛都相互崇拜,所以可以看成一个点
如果跑一次强连通分量 然后进行缩点,就化为一个有向无环图
显然 无环图中 只有出度为0的点才可能被所有牛崇拜
所以 构建一个缩点后的图的反向图 对出度为0的点进行dfs 如果能dfs到所有新图的点 这个点的牛就是ans
而且 不难发现 如果有>=2个点出度为0 必定不存在一个点能到达所有点 所以ans=0
跑一遍kosaraju O(n+m) 构建反向图后 dfs也只可能跑1次
所以最终复杂度为O(n+m)


#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#include<string>
#include<vector>
#include<deque>
#include<queue>
#include<algorithm>
#include<set>
#include<map>
#include<stack>
#include<time.h>
#include<math.h>
#include<list>
#include<cstring>
#include<fstream>
//#include<memory.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define INF 1000000007
#define pll pair<ll,ll>
#define pid pair<int,double>

const int N=10000+5;
const int M=50000+5;

struct Edge{
    int fr,to,next,nextRev;
}edge[M];

int head[N],headRev[N];//原图 反向边的图

void addEdge(int k,int u,int v){
    edge[k].fr=u;
    edge[k].to=v;
    edge[k].next=head[u];
    edge[k].nextRev=headRev[v];
    head[u]=headRev[v]=k;
}

int visited[N];//dfs标记
int num[N];//dfs1遍历的顺序
int scc[N];//scc[i]=第i个点属于第scc[i]个强连通分量

int dfs1(int cur,int&sig){
    visited[cur]=true;
    for(int i=head[cur];i!=-1;i=edge[i].next){
        if(visited[edge[i].to]==false){
            dfs1(edge[i].to,sig);
        }
    }
    num[sig++]=cur;
    return 0;
}

int dfs2(int cur,int sig){
    visited[cur]=true;
    scc[cur]=sig;
    for(int i=headRev[cur];i!=-1;i=edge[i].nextRev){
        if(!visited[edge[i].fr]){
            dfs2(edge[i].fr,sig);
        }
    }
    return 0;
}

int kosaraju(int n){
    int sig=0;
    fill(visited,visited+n+1,false);
    for(int i=1;i<=n;++i){
        if(visited[i]==false){
            dfs1(i,sig);
        }
    }
    sig=1;
    fill(visited,visited+n+1,false);
    for(int i=n-1;i>=0;--i){
        int k=num[i];
        if(visited[k]==false){
            dfs2(k,sig++);
        }
    }
    return sig-1;
}

int newHead[N];//缩点后的图的反向图
struct NewEdge{
    int to,next;
}newEdge[M];
int outDeg[N];//出度
void newAddEdge(int k,int u,int v){
    newEdge[k].to=v;
    newEdge[k].next=newHead[u];
    newHead[u]=k;
}

void dfs(int cur,int&visN){
    ++visN;
    for(int i=newHead[cur];i!=-1;i=newEdge[i].next){
        dfs(newEdge[i].to,visN);
    }
}

int main()
{
    //freopen("/home/lu/文档/r.txt","r",stdin);
    //freopen("/home/lu/文档/w.txt","w",stdout);
    int n,m;
    scanf("%d%d",&n,&m);
    fill(head,head+n+1,-1);
    fill(headRev,headRev+n+1,-1);
    for(int i=0,u,v;i<m;++i){
        scanf("%d%d",&u,&v);
        addEdge(i,u,v);
    }
    int newN=kosaraju(n);//缩点后剩余点数
    fill(newHead,newHead+newN+1,-1);
    for(int i=0,k=0;i<m;++i){
        int u=scc[edge[i].fr],v=scc[edge[i].to];
        if(u==v)
            continue;
        ++outDeg[u];
        newAddEdge(k++,v,u);
    }
    int ans=0,visN;
    fill(num,num+newN+1,0);
    for(int i=1;i<=n;++i){
        ++num[scc[i]];//缩点后 第i个点包含原图中num[i]个点
    }
    for(int i=1;i<=newN;++i){
        if(outDeg[i]==0){
            visN=0;
            dfs(i,visN);
            if(visN==newN){
                if(ans==0)
                    ans+=num[i];
                else{
                    ans=0;
                    break;
                }
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值