poj2186-Popular Cows【Tarjan】+(染色+缩点)(经典)

<题目链接>

题目大意:

有N(N<=10000)头牛,每头牛都想成为most poluler的牛,给出M(M<=50000)个关系,如(1,2)代表1欢迎2,关系可以传递,但是不可以相互,即1欢迎2不代表2欢迎1,但是如果2也欢迎3那么1也欢迎3.
给出N,M和M个欢迎关系,求被所有牛都欢迎的牛的数量。

解题分析:

仔细思考后发现,其实题目就是问你,在给出的这个有向图中的所有连通分量中,是否存在唯一出度为0的连通分量,如果存在,则输出这个连通分量中所有点的个数。

//dfn[v]: 顶点v被访问的时间;
//low[v]: low数组的作用是让某个连通分量的每一个点的low值等于这个连通分量被访问时间最早的那个点,即确定连通分量中的根节点
//tp: 访问时间;
//Stack[] : 数组模拟栈,p: 栈的大小;
//vis[]: 顶点是否在栈中
//in[]: 缩点后的数组,cnt: 缩点后图中点的个数
//kt[]: 出度数组
#include <cstdio>
#include <stack>
#include <iostream>
#include <string.h>
#include <algorithm>
using namespace std;
typedef long long LL;

const int N=1e4+10;
const int M=5e4+10;

struct Node{
    int to;
    int next;
};
Node edge[M];         //链式前向星
int tol,head[M];    


int dfn[N],low[N],tp;   
int Stack[N],p;
bool vis[N];
int in[N],cnt;
int kt[N];

int n,m;

void add(int u,int v)       //存图
{
    edge[tol].to=v;
    edge[tol].next=head[u];
    head[u]=tol++;
}

void init()
{
    //初始化链式前向星
    tol=0;
    memset(head,-1,sizeof(head));
    memset(vis,0,sizeof(vis));      //初始化顶点都不在栈里面
    memset(dfn,0,sizeof(dfn));      //初始化访问时间为0
    tp=p=0;                         //初始化时间和栈大小都为0
    cnt=0;                          //缩点初始化,可以理解有cnt种点集
}

void Tarjan(int u)
{
    int v;
    dfn[u]=low[u]=++tp;     //初始化dfn=low=访问时间      
    Stack[++p]=u;                //节点入栈
    vis[u]=true;                 //入栈标记

    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        v=edge[i].to;   
        if(!dfn[v])         //如果还未被访问
        {
            Tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(vis[v])    //在栈里;
            low[u]=min(low[u],dfn[v]);
    }


    int temp;
    if(dfn[u]==low[u])      //如果是这个强连通分量的根;          
    {
        cnt++;                  //缩点标记,给每个连通块染色,打上标记序号
        while(1)                
        {
            temp=Stack[p];      //取元素
            vis[temp]=false;    //出栈标记
            in[temp]=cnt;       //缩点,给该连通块中的所有点染色
            --p;
            if(temp==u)         //如果与u相等,退出
                break;
        }
    }
}

void Solve()
{
    int u,i,v;
    memset(kt,0,sizeof(kt));        //初始化出度数组

    for(u=1;u<=n;u++)
    {
        for(i=head[u];i!=-1;i=edge[i].next)
        {
            v=edge[i].to;
            if(in[u]!=in[v])            //如果不属于同一个集合
                kt[in[u]]++;            //这个集合的出度++
        }
    }
    int sum=0;              //出度个数
    int k;                  //标记是哪个点集
    int ans=0;              //答案
    for(int i=1;i<=cnt;i++)
    {
        if(!kt[i])
        {
            sum++;      //统计出度为0的连通块的数目
            k=i;        //标记这个联通块
        }
    }
    if(sum==1)         //如果只有一个连通块出度为0,则符合条件
    {
        for(int i=1;i<=n;i++)   //找到这个联通块中点的个数
        {
            if(in[i]==k)        //标记为k的连通块中的点        
                ans++;
        }
        printf("%d\n",ans);
    }
    else
        puts("0");
}

int main()
{
    int u,v;
    while(~scanf("%d%d",&n,&m))
    {
        init();
        while(m--)
        {
            scanf("%d%d",&u,&v);
            add(u,v);
        }
        //Tarjan()
        for(int i=1;i<=n;i++)
        {
            if(!dfn[i])     //如果还没有被访问过
                Tarjan(i);
        }
        Solve();
    }
    return 0;
}

2018-08-16

猜你喜欢

转载自www.cnblogs.com/00isok/p/9489503.html
今日推荐