poj 2186 Popular Cows 【强连通分量Tarjan算法 + 树问题】

题目地址:http://poj.org/problem?id=2186

Popular Cows

Time Limit: 2000MS   Memory Limit: 65536K
Total Submissions: 27496   Accepted: 11059

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.

题目:样例解释,3头牛,3条边。 1崇拜2, 2崇拜1, 2崇拜3。  3号牛能获得出自己之外的所有牛的崇拜,这样的牛有多少?输出数量。

注意:崇拜的关系是可以往上层传递的。1->2, 2->3, 可以得到:1->3.

通过Tarjan算法,我们会把所有的节点划分到不同的枪连通分量中。根据强连通分量的定义,处在同一个强连通分量中的节点,一会是互相仰慕的.

把每个强连通分量看成是一个节点。这样就构造成一棵树(树边是有向的),看这个树有几个出度为0的强连通分量。如果仅有一个,则会存在正确

答案。否则不可能存在满足题目的可能,只能输出0。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#define N 10000+10
#define M 51000+10

using namespace std;
int n, m;
struct node
{
    int to;
    int next;
}edge[M];//保证存下所有的边

int head[N], dfn[N], low[N], vis[N];
int stack[N], num[N], du[N];
int counter, top, cut, cnt;
/*
  dfn[]:记录搜索时各顶点的访问次序,即深度优先数
  low[u]:每个节点定义一个low值 low[u]是从u或u的子孙出发通过回边可以到达
         的最低深度优先数
  注意:dfn[]在搜索前进时进行统计 low[]值是在回退的时候进行计算
  du[]:表示出度
*/
void init()
{
    memset(head, -1, sizeof(head));
    memset(vis, 0, sizeof(vis));
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0, sizeof(low));
    memset(num, 0, sizeof(num));
    memset(du, 0, sizeof(du));
    counter=1;//begin 1
    cnt=0;//边 计数
    top=0;// stack top
    cut=0;
}

void addedge(int u, int v)
{
    edge[cnt].to=v;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}//静态数组 倒着建边

void dfs(int u)
{
    dfn[u]=low[u]=counter++;//counter的值从1开始
    vis[u]=1;//标记访问
    stack[top++]=u;
    //遍历从u节点发出的边
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v=edge[i].to;
        if(!vis[v]){//这是一条树边
            dfs(v);
            low[u]=min(low[u], low[v]);//回溯的时候比较
        }
        else if(vis[v]){
            low[u]=min(low[u], dfn[v]);
        }
    }
    if(low[u]==dfn[u]){
        cut++;//表示已经找到了一个强连通分量
        while(top>0 && stack[top]!=u){
            top--;
            vis[stack[top]]=2;//
            num[stack[top]]=cut;//确定当前这个节点属于哪个强连通分量
        }
    }
}

int main()
{
    int i, j;
    while(scanf("%d %d", &n, &m)!=EOF)
    {
        init();//进行初始化
        int u, v;
        for(i=1; i<=m; i++){
            scanf("%d %d", &u, &v);
            addedge(u, v);
        }//建立单向边

        for(i=1; i<=n; i++){
            if(!vis[i]){
                dfs(i);
            }
        }//不知道为什么在此处计数一下连通分量的数目 如果大于1 直接输出0 这样提交 会WA。不写就对

        for(i=1; i<=n; i++){
            for(j=head[i]; j!=-1; j=edge[j].next){
                int v=edge[j].to;
                if(num[i]!=num[v])//如果连通分量的编号不等
                    du[num[i]]++;//出度++
            }
        }
        int pos; int dd=0;
        for(i=1; i<=cut; i++){
            if(du[i]==0){
                dd++; pos=i;
            }
        }
        //printf("pos = %d\n", pos);
        if(dd==1){
        int ans=0;
        for(i=1; i<=n; i++){
            if(num[i]==pos)
                ans++;
        }
        printf("%d\n", ans );
        }
        else printf("0\n");
    }
    return 0;
}

使用STL-stack模拟的,注意在栈弹出节点的地方的写法,在循环跳出之后再弹出一次,因为遇到u==v的时候就跳出了,而u没有被弹出。

上面的数组模拟的stack操作也可以再修改一下。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stack>
#include <algorithm>
#define N 10000+10
#define M 51000+10

using namespace std;
int n, m;
struct node
{
    int to;
    int next;
}edge[M];//保证存下所有的边

int head[N], dfn[N], low[N], vis[N];
int num[N], du[N];
int counter, top, cut, cnt;
/*
  dfn[]:记录搜索时各顶点的访问次序,即深度优先数
  low[u]:每个节点定义一个low值 low[u]是从u或u的子孙出发通过回边可以到达
         的最低深度优先数
  注意:dfn[]在搜索前进时进行统计 low[]值是在回退的时候进行计算
  du[]:表示出度
*/
stack<int>q;
void init()
{
    memset(head, -1, sizeof(head));
    memset(vis, 0, sizeof(vis));
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0, sizeof(low));
    memset(num, 0, sizeof(num));
    memset(du, 0, sizeof(du));
    counter=1;//begin 1
    cnt=0;//边 计数
    top=0;// stack top
    cut=0;
}

void addedge(int u, int v)
{
    edge[cnt].to=v;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}//静态数组 倒着建边

void dfs(int u)
{
    dfn[u]=low[u]=counter++;//counter的值从1开始
    vis[u]=1;//标记访问
    q.push(u);
    //遍历从u节点发出的边
    for(int i=head[u]; i!=-1; i=edge[i].next){
        int v=edge[i].to;
        if(!vis[v]){//这是一条树边
            dfs(v);
            low[u]=min(low[u], low[v]);//回溯的时候比较
        }
        else if(vis[v]){
            low[u]=min(low[u], dfn[v]);
        }
    }
    if(low[u]==dfn[u]){
        cut++;//表示已经找到了一个强连通分量
        while(!q.empty() && q.top()!=u){
            vis[q.top()]=2;//
            num[q.top()]=cut;//确定当前这个节点属于哪个强连通分量
            q.pop();
        }
        vis[q.top()]=2;//
        num[q.top()]=cut;//确定当前这个节点属于哪个强连通分量
        q.pop(); //还要将u节点弹出
    }
}

int main()
{
    int i, j;
    while(scanf("%d %d", &n, &m)!=EOF)
    {
        init();//进行初始化
        int u, v;
        for(i=1; i<=m; i++){
            scanf("%d %d", &u, &v);
            addedge(u, v);
        }//建立单向边

        int cur=0;
        for(i=1; i<=n; i++){
            if(!vis[i]){
                dfs(i); cur++;
            }
        }

        for(i=1; i<=n; i++){
            for(j=head[i]; j!=-1; j=edge[j].next){
                int v=edge[j].to;
                if(num[i]!=num[v])//如果连通分量的编号不等
                    du[num[i]]++;//出度++
            }
        }
        int pos; int dd=0;
        for(i=1; i<=cut; i++){
            if(du[i]==0){
                dd++; pos=i;
            }
        }
        if(dd==1){
        int ans=0;
        for(i=1; i<=n; i++){
            if(num[i]==pos)
                ans++;
        }
        printf("%d\n", ans );
        }
        else printf("0\n");
    }
    return 0;
}
时间: 2025-01-01 11:08:39

poj 2186 Popular Cows 【强连通分量Tarjan算法 + 树问题】的相关文章

POJ 2186 Popular Cows --强连通分量

题意:给定一个有向图,问有多少个点由任意顶点出发都能达到. 分析:首先,在一个有向无环图中,能被所有点达到点,出度一定是0. 先求出所有的强连通分支,然后把每个强连通分支收缩成一个点,重新建图,这样,这个有向图就变成了一个有向无环图. 在这个新的图中,只需知道出度为0的点有几个即可. 如果出度为0的点超过1个,则输出0:否则输出出度为0的点所代表的那个强连通分支的分量数即可. 用Tarjan求强连通分量 代码: #include <iostream> #include <cstdio&g

POJ 2186 - Popular Cows - 强连通分量,缩点

题目大意: 给定一个含N个点.M条边的有向图,求其中有多少个点,可以由其他任意一点出发到达它? N<=1e4,M<=5e4. 为了描述和编程简便,我们建立原图的反图,这样问题转化为:有多少个点满足从它出发可以到达其他任意一点. 若无特殊说明,以下所指的图均为反图. 引理1:满足条件的所有点必然在同一强连通分量内. 证明很简单,如果它们不在同一强连通分量内,那么其中必然有两点x,y使得x→y的路径不存在,与题目要求矛盾. 我们考虑求出该图的所有强连通分量,然后对于每个强连通分量,检验从其中任一点

POJ 2186 Popular Cows 强连通分量模板

题意 强连通分量,找独立的块 强连通分量裸题 #include <cstdio> #include <cstdlib> #include <cstring> #include <string> #include <algorithm> #include <iostream> using namespace std; const int maxn = 50005; int n, m; struct Edge { int v, next;

POJ 2186 -- Popular Cows【强连通分支 &amp;&amp; Tarjan缩点】

Popular Cows Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 27198   Accepted: 10963 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 &

poj 2186 Popular Cows 强连通缩点

[题意]:给出一群牛中,一头牛认为另一头牛受欢迎的m对关系,关系式可以传递的,即若a认为b受欢迎,b认为c受欢迎,那么a也认为c受欢迎.求最多有多少头牛被所有的牛认为受欢迎. [思路]:把这些关系形成的强连通图缩成点,就形成了一有向无环图,这个图里的出度为0的点(有且只有一个)就是被所有牛认为受欢迎的点,说明若这个点原来是强连通图就要求出这个强连通图里的总点数, 1 #include<iostream> 2 #include<stdio.h> 3 #include<strin

poj - 2186 Popular Cows (强连通)

http://poj.org/problem?id=2186 给定n头牛,m个关系,每个关系a,b表示a认为b是受欢迎的,但是不代表b认为a是受欢迎的,关系之间还有传递性,假如a->b,b->c 则a->c,问有多少头牛被其他所有的牛欢迎. 统计出度为0的点,如果不为1,则表示不存在这样的牛,为1的话就输出这个集合点的数量. 1 #include <iostream> 2 #include <cstdio> 3 #include <cmath> 4 #

强连通分量分解 Kosaraju算法 (poj 2186 Popular Cows)

poj 2186 Popular Cows 题意: 有N头牛, 给出M对关系, 如(1,2)代表1欢迎2, 关系是单向的且可以传递, 即1欢迎2不代表2欢迎1, 但是如果2也欢迎3那么1也欢迎3. 求被所有牛都欢迎的牛的数量. 限制: 1 <= N <= 10000 1 <= M <= 50000 思路: Kosaraju算法, 看缩点后拓扑序的终点有多少头牛, 且要判断是不是所有强连通分量都连向它. Kosaraju算法,分拆完连通分量后,也完成了拓扑序. /*poj 2186

POJ 2186 Popular Cows (强连通分量)

POJ 2186 Popular Cows 链接:http://poj.org/problem?id=2186 题意:每头奶牛都梦想着成为牧群中最受奶牛仰慕的奶牛.在牧群中,有N 头奶牛,1≤N≤10,000,给定M 对(1≤M≤50,000)有序对(A, B),表示A 仰慕B.由于仰慕关系具有传递性,也就是说,如果A 仰慕B,B 仰慕C,则A 也仰慕C,即使在给定的M 对关系中并没有(A, C).你的任务是计算牧群中受每头奶牛仰慕的奶牛数量. 思路:首先可以知道,在同一个强连通分量内的点一定互

POJ 2186 Popular Cows -- tarjan 缩点

链接: POJ 2186 Popular Cows 题意: 每一头牛都希望在牛群里面备受瞩目,在一个牛群中有N头牛(1<=N<=10000),你被给予M(1<=M<=50000)个关系对,形式如(A,B),这意味着A牛认为B牛比它更受欢迎,由于这种欢迎度是满足传递性的,那么若是A牛认为B牛更受欢迎,B牛认为C牛更受欢迎,那么A牛也会认为C牛更受欢迎.你的任务是计算出被所有牛受欢迎的牛的个数. 输入: 第一行两个整数 N 和 M 第2 到 M + 1 行,两个分开的数 A,B,意味着