POJ 2186 Popular Cows(强连通分量缩点,Tarjan算法)

【题目链接】
http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=16578

【解题报告】
给你一个有向图,问你有多少个点可以被其它所有点访问到。
因为图中存在环,而环里的节点可以互相访问到,所以我们应当将一个环缩为一个点(这是不影响对题目的分析的)。
这样这个有向图就变成了DAG(有向无环图)。
容易看出,在这个图中,如果只存在一个节点出度为0,那么它可以被其它所有结点访问到。如果存在多个节点出度为0,那么本题就无解。(n个点被分为了多棵树,每颗树无法相互访问)

那么这道题的核心就在于缩点这个步骤。

关于缩点所需要的tarjan算法,在下面给出的链接里有相当清晰的说明,这里不再赘述。其它说明请参考代码注释。

ps:其实看懂了tarjan算法之后会觉得这个算法思路很易懂代码也相当简洁的。不过网上的资料往往直接描述算法,而缺乏对本身的分析,所以看起来很难懂。找了一些自己觉得不错的资料贴在后面,希望能帮助读者理解求解强连通分量的算法。

【参考资料】
《 深度理解链式前向星 》–Acdreamers
http://blog.csdn.net/acdreamers/article/details/16902023

《处理SCC(强连通分量问题)的Tarjan算法》–comzyh
https://comzyh.com/blog/archives/517/

《有向图强连通分量的Tarjan算法》–byvoid
https://www.byvoid.com/blog/scc-tarjan/

【参考代码】

#include<cstdio>
#include<iostream>
#include<cstring>
#include<stack>
#include<cmath>
using namespace std;
const int maxn=1e4+50;
const int maxm=5e4+50;

int head[maxn],LOW[maxn],DFN[maxn],id[maxn],cnt[maxn];
bool mark[maxn];
int N,M,time=0,scc=0;
stack<int>sta;

struct edge_t{
    int to,next;
}edge[maxm];

/*
DFN(u)为节点u搜索的次序编号(时间戳)
Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号
当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。
*/
void tarjan( int u )
{
    DFN[u]=LOW[u]=++time;
    sta.push(u);
    mark[u]=true;
    for( int k=head[u]; k!=-1; k=edge[k].next )
    {
        int v=edge[k].to;
        if(!DFN[v])
        {
            tarjan(v);
            LOW[u]=min( LOW[u],LOW[v] );
        }
        else if(mark[v])//v已经进入过栈但是还没出栈
        {
            LOW[u]=min(LOW[u],DFN[v]);
        }
    }
    if(DFN[u]==LOW[u])
    {
        scc++;
        int v;
        do
        {
            v=sta.top();
            sta.pop();
            id[v]=scc;  //v这个节点属于哪个scc
            mark[v]=false;
            cnt[scc]++;  //标记每个scc包含的节点数量
        }
        while(u!=v);
    }
}

void solve()
{
    memset(mark,0,sizeof mark);//用来标记节点是否在栈里
    memset(DFN,0,sizeof DFN);
    for( int i=1; i<=N; i++ )  if(!DFN[i])  tarjan(i);

    memset(mark,0,sizeof mark);//用来标记某个scc是否有出度
    for( int i=1; i<=N; i++ )
    {
        for( int k=head[i]; k!=-1; k=edge[k].next )
        {
            if(id[i]!=id[edge[k].to])
                mark[id[i]]=true;
        }
    }
    int pos=0,lab=0;
    for( int i=1; i<=scc; i++ )if(!mark[i])
    {
        lab++;
        pos=i;
    }
    if(lab>1)printf("0\n");
    else printf("%d\n",cnt[pos]);
}

int main()
{
    scanf("%d%d",&N,&M);
    memset(head,-1,sizeof head);
    memset(cnt,0,sizeof cnt);//记录每个scc有多少个节点
    for( int i=0; i<M; i++ )
    {
        int a,b;
        scanf("%d%d",&a,&b);
        edge[i].to=b;  //利用了类似前向星的保存方法
        edge[i].next=head[a];
        head[a]=i;
    }
    solve();

    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章