POJ2186 Popular Cows 【强连通分量】+【Kosaraju】+【Tarjan】+【Garbow】

Popular Cows

Time Limit: 2000MS   Memory Limit: 65536K
Total Submissions: 23445   Accepted: 9605

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

题意:能够转换成“给定一些有向路,求有多少个点能够由其余的随意点到达。

题解:第一道强连通分量的题,大致总结下Kosaraju算法:求强连通分量主要是为了简化图的构造,假设分量外的一个点能到达分量内的当中一个点,那么它必然能到达分量内的全部点,所以某种程度上。强连通分量能够简化成一个点。详细的求解过程是:1、随意选定一个点開始对原图进行深搜,记录每一个点离开时的时间(更确切的说是求每一个时间相应哪个点离开)。2、对原图的反图进行深搜,步骤一中最后离开的点最先開始深搜。每次将同一棵树中的点都哈希成同一个值。最后有多少棵树就有多少个强连通分量。

这题最后全部点都哈希完毕后实际上构成了一个DAG。假设新图中出度为0的点仅仅有一个那么有解,解为该出度为0的强连通分量中原来点的个数。若出度为0的点不止一个,那么无解,由于有两群牛互不崇拜,此时答案为0.在推断连通分量是否有出度时有个小技巧,就是在对反图DFS时若发现连接到的点已訪问且它的哈希值与当前訪问点的哈希值不同。那么这个被连接到的点相应的联通分量是有出度的。然后还需记录每一个连通分量的点数。

#include <stdio.h>
#include <string.h>
#define maxn 10002
#define maxm 50002

int head0[maxn], head1[maxn], id;
int count[maxn], num[maxn], hash[maxn];
struct Node{
    int t0, next0, t1, next1;
} E[maxm];
bool vis[maxn], out[maxn];

void addEdge(int u, int v)
{
    E[id].t0 = v; E[id].next0 = head0[u];
    head0[u] = id; E[id].t1 = u;
    E[id].next1 = head1[v]; head1[v] = id++;
}

void getMap(int n, int m)
{
    int i, u, v; id = 0;
    memset(head0, -1, sizeof(int) * (n + 1)); //save time
    memset(head1, -1, sizeof(int) * (n + 1));
    for(i = 0; i < m; ++i){
        scanf("%d%d", &u, &v);
        addEdge(u, v);
    }
}

void DFS0(int pos, int& sig)
{
    vis[pos] = 1; int i;
    for(i = head0[pos]; i != -1; i = E[i].next0){
        if(!vis[E[i].t0]) DFS0(E[i].t0, sig);
    }
    num[++sig] = pos;
}

void DFS1(int pos, int sig)
{
    vis[pos] = 1; hash[pos] = sig;
    int i; ++count[sig];
    for(i = head1[pos]; i != -1; i = E[i].next1){
        if(!vis[E[i].t1]) DFS1(E[i].t1, sig);
        else if(hash[E[i].t1] != hash[pos]) out[hash[E[i].t1]] = 1;
    }
}

void solve(int n) //Kosaraju
{
    int i, sig = 0, tmp = 0, ans;
    memset(vis, 0, sizeof(bool) * (n + 1));
    for(i = 1; i <= n; ++i)
        if(!vis[i]) DFS0(i, sig);
    memset(vis, 0, sizeof(bool) * (n + 1));
    memset(count, 0, sizeof(int) * (n + 1));
    memset(out, 0, sizeof(bool) * (n + 1));
    i = sig; sig = 0;
    for(; i; --i)
        if(!vis[num[i]]) DFS1(num[i], ++sig);
    for(i = 1; i <= sig; ++i)
        if(!out[i]) ++tmp, ans = count[i];
    //printf("sig%d\n", sig);
    if(tmp == 1) printf("%d\n", ans);
    else printf("0\n");
}

int main()
{
    int n, m;
    while(scanf("%d%d", &n, &m) == 2){
        getMap(n, m);
        solve(n);
    }
    return 0;
}

Tarjan解法:

#include <stdio.h>
#include <string.h>
#define maxn 10002
#define maxm 50002

int head[maxn], vis[maxn], id, id2, scc_num, sec;
int dfn[maxn], low[maxn], sta[maxn], count[maxn];
bool out[maxn];
struct Node{
    int to, next;
} E[maxm];

int min(int a, int b){
    return a < b ?

a : b;
}

void addEdge(int u, int v)
{
    E[id].to = v;
    E[id].next = head[u];
    head[u] = id++;
}

void getMap(int n, int m)
{
    int i, u, v; id = 0;
    memset(head, -1, sizeof(int) * (n + 1));
    memset(vis, 0, sizeof(int) * (n + 1));
    memset(out, 0, sizeof(bool) * (n + 1));
    memset(count, 0, sizeof(int) * (n + 1));
    for(i = 0; i < m; ++i){
        scanf("%d%d", &u, &v);
        addEdge(u, v);
    }
}

void DFS(int pos) //强连通分量必然是该树的子树
{
    dfn[pos] = low[pos] = ++sec;
    vis[pos] = 1; sta[id2++] = pos;
    int i, u, v;
    for(i = head[pos]; i != -1; i = E[i].next){
        v = E[i].to;
        if(!vis[v]) DFS(v);
        if(vis[v] == 1)
            low[pos] = min(low[pos], low[v]);
    }
    if(dfn[pos] == low[pos]){
        ++scc_num;
        do{
            ++count[scc_num];
            u = sta[--id2];
            low[u] = scc_num;
            vis[u] = 2;
        } while(u != pos);
    }
}

void solve(int n) //Tarjan
{
    int i, j, ok = 0, ans; sec = id2 = scc_num = 0;
    for(i = 1; i <= n; ++i)
        if(!vis[i]) DFS(i);
    for(i = 1; i <= n; ++i)
        for(j = head[i]; j != -1; j = E[j].next)
            if(low[i] != low[E[j].to]){
                out[low[i]] = 1; break;
            }
    for(i = 1; i <= scc_num; ++i)
        if(!out[i]){
            if(++ok > 1) break;
            ans = count[i];
        }
    if(ok != 1) printf("0\n");
    else printf("%d\n", ans);
}

int main()
{
    int n, m;
    while(scanf("%d%d", &n, &m) == 2){
        getMap(n, m);
        solve(n);
    }
    return 0;
}

Garbow解法:与Tarjan思想同样,仅仅是实现方式略有不同,效率更高一些。

#include <stdio.h>
#include <string.h>
#define maxn 10002
#define maxm 50002
//sta2用以维护当前连通分量的根
int head[maxn], id, sta1[maxn], id1, sta2[maxn], id2;
int low[maxn], scc[maxn], sccNum, sec, count[maxn];
struct Node{
    int to, next;
} E[maxm];
bool out[maxn];

void addEdge(int u, int v)
{
    E[id].to = v;
    E[id].next = head[u];
    head[u] = id++;
}

void getMap(int n, int m)
{
    int i, u, v; id = 0;
    memset(head, -1, sizeof(int) * (n + 1));
    for(i = 0; i < m; ++i){
        scanf("%d%d", &u, &v);
        addEdge(u, v);
    }
}

void Garbow(int pos)
{
    low[pos] = ++sec;
    sta1[id1++] = sta2[id2++] = pos;
    for(int i = head[pos]; i != -1; i = E[i].next){
        if(!low[E[i].to]) Garbow(E[i].to);
        else if(!scc[E[i].to]){
            while(low[sta2[id2-1]] > low[E[i].to]) --id2;
        }
    }
    if(pos == sta2[id2-1]){
        int v; ++sccNum; --id2;
        do{
            v = sta1[--id1];
            scc[v] = sccNum;
            ++count[sccNum];
        } while(sta1[id1] != pos);
    }
}

void solve(int n)
{
    int i, j; id1 = id2 = sec = sccNum = 0;
    memset(low, 0, sizeof(int) * (n + 1));
    memset(scc, 0, sizeof(int) * (n + 1));
    memset(count, 0, sizeof(int) * (n + 1));
    memset(out, 0, sizeof(bool) * (n + 1));
    for(i = 1; i <= n; ++i)
        if(!low[i]) Garbow(i);
    for(i = 1; i <= n; ++i)
        for(j = head[i]; j != -1; j = E[j].next)
            if(scc[i] != scc[E[j].to]){
                out[scc[i]] = 1; break;
            }
    int tmp = 0, ans;
    for(i = 1; i <= sccNum; ++i)
        if(!out[i]){
            if(++tmp > 1){
                ans = 0; break;
            }
            ans = count[i];
        }
    printf("%d\n", ans);
}

int main()
{
    int n, m;
    while(scanf("%d%d", &n, &m) == 2){
        getMap(n, m);
        solve(n);
    }
    return 0;
}

时间: 2025-01-02 06:49:32

POJ2186 Popular Cows 【强连通分量】+【Kosaraju】+【Tarjan】+【Garbow】的相关文章

poj2186 Popular Cows --- 强连通

给一个有向图,问有多少结点是其他所有结点都可以到达的. 等价于,在一个有向无环图上,找出度为0 的结点,如果出度为0的结点只有一个,那么这个就是答案,如果大于1个,则答案是0. 这题有环,所以先缩点.求唯一出度为0的强连通分量. #include<cstdio> #include<cstring> #include<vector> #include<queue> #include<iostream> #define inf 0x3f3f3f3f

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;

POJ2186 Popular Cows ,有向图, Tarjan算法

题意: 给定一个有向图,求有多少个顶点是由任何顶点出发都可达的. 顶点数<= 10,000,边数 <= 50,000 定理: 有向无环图中唯一出度为0的点,一定可以由任何点出发均可达 (由于无环,所以从任何点出发往前走,必然终止于一个出度为0的点) 1. 求出所有强连通分量 2. 每个强连通分量缩成一点,则形成一个有向无环图DAG. 3. DAG上面如果有唯一的出度为0的点,则该点能被所有的点可达.那么该点所代表的连通分量上的所有的原图中的点,都能被原图中的所有点可达,则该连通分量的点数,就是

强连通分量tarjan缩点——POJ2186 Popular Cows

这里的Tarjan是基于DFS,用于求有向图的强联通分量. 运用了一个点dfn时间戳和low的关系巧妙地判断出一个强联通分量,从而实现一次DFS即可求出所有的强联通分量. §有向图中, u可达v不一定意味着v可达u.    相互可达则属于同一个强连通分量    (Strongly Connected Component, SCC) §有向图和它的转置的强连通分量相同 §所有SCC构成一个DAG(有向无环图) dfn[u]为节点u搜索的次序编号(时间戳),即首次访问u的时间 low[u]为u或u的

POJ2186 Popular Cows 【强连通分量Kosaraju】

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

强连通分量的模版 Kosaraju+Tarjan+Garbow

PS:在贴出代码之前,我得说明内容来源——哈尔滨工业大学出版的<图论及应用>.虽然有一些错误的地方,但是不得不说是初学者该用的书. 从效率的角度来说,Kosaraju <Tarjan<Garbow.一般网上有前两种的代码和分析.Garbow算法是Tarjan的另一种实现,但是Garbow效率更高. 不过从复杂度来说,三种算法的时间(空间)复杂度都是O(m +n). 模版的调用方式很简单,初始化,建图,调用Tarjan(n)或者Kosaraju(n)或者 Garbow(n), scc

强连通分量的Tarjan算法

资料参考 Tarjan算法寻找有向图的强连通分量 基于强联通的tarjan算法详解 有向图强连通分量的Tarjan算法 处理SCC(强连通分量问题)的Tarjan算法 强连通分量的三种算法分析 Tarjan算法详解理解集合 ppt图解分析下载 强连通分量 强连通分量(strongly connected component)是图论中的概念.图论中,强连通图指每一个顶点皆可以经由该图上的边抵达其他的每一个点的有向图.意即对于此图上每一个点对(Va,Vb),皆存在路径Va→Vb以及Vb→Va.强连通

【转载】有向图强连通分量的Tarjan算法

from byvoid [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components). 下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达.{5},{6}也分别是两个强连通分量. 直接根据定义,用双向遍历取交集的方法求强连通分量,时间复杂度为