POJ 2186【Tarjan算法(模板_缩点)】

题意:
给定一个有向图,求有多少个顶点是由任何顶 点出发都可达的。
思路(摘自PKU_强连通分量PDF):
求出所有强连通分量
每个强连通分量缩成一点,则形成一个有 向无环图DAG。
DAG上面如果有唯一的出度为0的点,则该点 能被所有的点可达。那么该点所代表的连通分 量上的所有的原图中的点,都能被原图中的所 有点可达,则该连通分量的点数,就是答案。
DAG上面如果有不止一个出度为0的点,则 这些点互相不可达,原问题无解,答案为0

#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long LL;

/*
N 1E4  M 5E4
*/

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

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

//dfn[v]: 顶点v被访问的时间;
//low[v]: 与顶点v邻接的未删除的顶点u的low[u]和low[v]的最小值,初始时low[v]=dfn[v];
//tp: 访问时间;
//Stack[] : 数组模拟栈,p: 栈的大小;
//vis[]: 顶点是否在栈中
//in[]: 缩点后的数组,cnt: 缩点后图中点的个数
//kt[]: 出度数组
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=访问时间       尤其注意这个++tp还是tp++,我们初始化时间是0,所以之后访问到的时间就一定>0;
    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 Fun()
{
    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++;
            k=i;
        }
    }
    if(sum==1)
    {
        for(int i=1;i<=n;i++)
        {
            if(in[i]==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);
        }
        Fun();
    }
    return 0;
}

你可能感兴趣的:(tarjan,图论)