【題目鏈接】
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;
}