【题目链接】
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;
}