首页 > 代码库 > POJ2186 Popular Cows【Tarjan】【强连通分量】

POJ2186 Popular Cows【Tarjan】【强连通分量】

题目连接:

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


题目大意:

每头奶牛都希望自己成为最欢迎的那头牛。给你N头牛,M个崇拜关系(A,B)。意思是牛A

崇拜牛B。特别是,如果牛A崇拜牛B,牛B崇拜牛C,那么牛A也崇拜牛C。那么问题来了:

请计算出被所有牛崇拜的牛的个数。


思路:

把崇拜关系(A,B)看做是一条有向边,并且,我们发现牛的崇拜关系具有传递性。么只要

牛A有一条路径连向牛B,就可以判定牛A崇拜牛B。于是,被所有牛崇拜的牛就是所有的点

都存在一条路径连向它的有向路径。这道题中,将原图强连通分量缩点后构成了DAG,那么

如果新图中出度为0的点只有一个,则有解,为该出度为0的点的强连通分量中点的个数。

出度为0的点的个数不止一个,那么无解。因为至少有两头牛互相不崇拜。

之前用Kosaraju算法来做,这次用Tarjan算法来做。


描述一下Tarjan算法:

对原图进行优先搜索,每个强连通分量是原图深度优先搜索的子树。那么我们就可以通过确

定每个强连通分量子树的根,根据这些根,从树的最底层开始,一个一个去处强连通分量。

使用两个数组:dfn[v]来表示顶点v被访问的时间,low[v]表示为顶点v邻接的未删除的顶点

u的low[u]和顶点v的low[v]的最小值(low[v]初始化为dfn[v])。这是为了使同一强连通分量的

low[]值都相同,且值与该强连通分量的根的访问时间dfn[]相等,从而就确定了强连通分量

根和其属于强连通分量的点。

这样,如果发现了low[v] == dnf[v],那么,该点v就是一个强连通分量的根。因为如果该点

v不是强连通分量的根,那么一定属于另一个强连通分量,而且该点v的根就是当前顶点v的祖

宗,那就存在包含当前顶点v到其祖宗的回路,可知顶点v的low[v]值一定是比顶点v的访问时

间dfn[v]更小的值,应为顶点v所在强连通分量的根的dfn[]值。

确定了强连通分量的根后,怎么来取出强连通分量呢?

如果当前节点v为一个强连通分量的根,那么它的强连通分量一定是以该节点v为根节点的子

树。在深度优先遍历的时候维护一个堆栈,每次访问一个新节点,就压入堆栈。由于当前节

点v是这个强连通分量中最先压入堆栈的,那么在当前节点v以后压入堆栈并且仍在堆栈中的

节点都属于这个强连通分量。假设一个节点u在当前节点v压入堆栈后压入并且还存在,同时

节点u不属于该强连通分量。那么一定属于另一个强连通分量,但当前节点v是节点u的根的

祖宗,那么这个强连通分量在之前已经被取出。

具体做法:

(1)访问一个没有被访问过的节点v;否则结束。

(2)初始化dfn[v]和low[v]。

对于节点v的所有邻接顶点u:

1.如果没有访问过,转到步骤(2),同时维护low[v]。

2.如果访问过,但没有删除,维护low[v]。

如果low[v] == dfn[v],那么取出相应的强连通分量。

参考:ACM-ICPC程序设计系列——图论及应用 P115~117


#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN = 10010;
const int MAXM = 50050;

struct EdgeNode
{
    int to;
    int next;
}Edges[MAXM];

int Head[MAXN],vis[MAXN],low[MAXN];
int dfn[MAXN],Stack[MAXN],outdegree[MAXN],Count[MAXN],m,id;

void AddEdges(int u,int v)
{
    Edges[id].to = v;
    Edges[id].next = Head[u];
    Head[u] = id++;
}

int TarBFS(int pos,int lay,int &scc)
{
    vis[pos] = 1;
    low[pos] = lay;     //初始为开始时间
    dfn[pos] = lay;     //初始为开始时间
    Stack[++m] = pos;   //将当前未处理节点入栈,回溯时可以判断栈顶到栈中的结点是否为同一个强连通分量
    for(int i = Head[pos]; i != -1; i = Edges[i].next)  //枚举每一条边
    {
        if(!vis[Edges[i].to])
            TarBFS(Edges[i].to,++lay,scc);
        if(vis[Edges[i].to] == 1)
            low[pos] = min(low[pos],low[Edges[i].to]);
    }

    if(dfn[pos] == low[pos])    //缩点,low[]相同的结点属于同一个强连通分量
    {
        ++scc;
        do
        {
            Count[scc]++;       //强连通分量内的点数
            low[Stack[m]] = scc;
            vis[Stack[m]] = 2;
        }while(Stack[m--] != pos);
    }
    return 0;
}

int Tarjan(int N)
{
    int scc = 0, temp = 0, ans, lay = 1;
    m = 0;
    memset(vis,0,sizeof(vis));
    memset(low,0,sizeof(low));
    memset(dfn,0,sizeof(dfn));
    for(int i = 1; i <= N; ++i)
        if(vis[i] == 0)
            TarBFS(i,lay,scc);

    for(int i = 1; i <= N; ++i)
    {
        for(int j = Head[i]; j != -1; j = Edges[j].next)
            if(low[i] != low[Edges[j].to])
            {
                outdegree[low[i]] = 1;
                break;
            }
    }

    for(int i = 1;i <= scc; ++i)
    {
        if(! outdegree[i])
        {
            if(++temp > 1)
                break;
            ans = Count[i];
        }
    }

    if(temp != 1)
        return 0;
    return ans;
}
int main()
{
    int N,M,u,v;
    while(~scanf("%d%d",&N,&M))
    {
        memset(Head,-1,sizeof(Head));
        memset(outdegree,0,sizeof(outdegree));
        memset(Count,0,sizeof(Count));
        id = 0;
        for(int i = 0; i < M; ++i)
        {
            scanf("%d%d",&u,&v);
            AddEdges(u,v);
        }
        int ans = Tarjan(N);
        printf("%d\n",ans);
    }


    return 0;
}


POJ2186 Popular Cows【Tarjan】【强连通分量】