題目簡(jiǎn)述:n頭奶牛,給出若干個(gè)歡迎關(guān)系a b,表示a歡迎b,歡迎關(guān)系是單向的,但是是可以傳遞的。另外每個(gè)奶牛都是歡迎他自己的。求出被所有的奶牛歡迎的奶牛的數(shù)目。
模型轉(zhuǎn)換:N個(gè)頂點(diǎn)的有向圖,有M條邊(N≤10000,M≤50000)。求一共有多少個(gè)點(diǎn),滿(mǎn)足這樣的條件:所有其它的點(diǎn)都可以到達(dá)這個(gè)點(diǎn)。
首先,這個(gè)題的N和M都非常大,硬做是肯定不行的??紤]如果這個(gè)圖是一棵樹(shù),那么問(wèn)題就變的很簡(jiǎn)單了,因?yàn)橹炼嘤幸粋€(gè)點(diǎn)滿(mǎn)足條件,這個(gè)點(diǎn)滿(mǎn)足條件的充要條件是:這個(gè)點(diǎn)是樹(shù)中唯一的出度為0的點(diǎn)。
那么我們能否把圖轉(zhuǎn)化為樹(shù) 有向無(wú)回路圖DAG(感謝網(wǎng)友提出)呢?首先可以想到的是,如果圖中包含有環(huán),那么就可以把這個(gè)環(huán)縮成一個(gè)點(diǎn),因?yàn)榄h(huán)中的任意兩個(gè)點(diǎn)可以到達(dá),環(huán)中所有的點(diǎn)具有相同的性質(zhì),即它們分別能到達(dá)的點(diǎn)集都是相同的,能夠到達(dá)它們的點(diǎn)集也是相同的。那么是否只有環(huán)中的點(diǎn)才具有相同的性質(zhì)呢?進(jìn)一步的考慮,圖中的每一個(gè)極大強(qiáng)連通分支中的點(diǎn)都具有相同的性質(zhì)。所以,如果把圖中的所有極大強(qiáng)連通分支求出后,就可以把圖收縮成一棵樹(shù) DAG,問(wèn)題就迎刃而解了。
預(yù)備知識(shí):有向圖的強(qiáng)連通分量的求法,這個(gè)和求割點(diǎn)的算法差不多。
算法框架:對(duì)有向圖求強(qiáng)連通分量,然后找出所有獨(dú)立的強(qiáng)連通分量(所謂獨(dú)立,就是該連通分量里面的點(diǎn)到外面的點(diǎn)沒(méi)有通路,當(dāng)然,連通分量外的點(diǎn)是可以有路到強(qiáng)連通分量?jī)?nèi)的點(diǎn)的),如果獨(dú)立的強(qiáng)連通分量的數(shù)目只有一個(gè),那么,就輸出這個(gè)強(qiáng)連通分量?jī)?nèi)解的個(gè)數(shù),否則輸出無(wú)解。
算法證明:
1:假設(shè)a和b都是最受歡迎的cow,那么,a歡迎b,而且b歡迎a,于是,a和b是屬于同一個(gè)連通分量?jī)?nèi)的點(diǎn),所有,問(wèn)題的解集構(gòu)成一個(gè)強(qiáng)連通分量。
2:如果某個(gè)強(qiáng)連通分量?jī)?nèi)的點(diǎn)a到強(qiáng)連通分量外的點(diǎn)b有通路,因?yàn)閎和a不是同一個(gè)強(qiáng)連通分量?jī)?nèi)的點(diǎn),所以b到a一定沒(méi)有通路,那么a不被b歡迎,于是a所在的連通分量一定不是解集的那個(gè)連通分量。
3:如果存在兩個(gè)獨(dú)立的強(qiáng)連通分量a和b,那么a內(nèi)的點(diǎn)和b內(nèi)的點(diǎn)一定不能互相到達(dá),那么,無(wú)論是a還是b都不是解集的那個(gè)連通分量,問(wèn)題保證無(wú)解。
4:如果圖非連通,那么,至少存在兩個(gè)獨(dú)立的連通分量,問(wèn)題一定無(wú)解。
第一次用前向星存儲(chǔ)圖,在不方便開(kāi)臨街矩陣和臨街表的情況下,用前向星存儲(chǔ)邊,再開(kāi)一個(gè)數(shù)組存儲(chǔ)開(kāi)始和結(jié)束的位置,很方便。
模型轉(zhuǎn)換:N個(gè)頂點(diǎn)的有向圖,有M條邊(N≤10000,M≤50000)。求一共有多少個(gè)點(diǎn),滿(mǎn)足這樣的條件:所有其它的點(diǎn)都可以到達(dá)這個(gè)點(diǎn)。
首先,這個(gè)題的N和M都非常大,硬做是肯定不行的??紤]如果這個(gè)圖是一棵樹(shù),那么問(wèn)題就變的很簡(jiǎn)單了,因?yàn)橹炼嘤幸粋€(gè)點(diǎn)滿(mǎn)足條件,這個(gè)點(diǎn)滿(mǎn)足條件的充要條件是:這個(gè)點(diǎn)是樹(shù)中唯一的出度為0的點(diǎn)。
那么我們能否把圖轉(zhuǎn)化為
預(yù)備知識(shí):有向圖的強(qiáng)連通分量的求法,這個(gè)和求割點(diǎn)的算法差不多。
算法框架:對(duì)有向圖求強(qiáng)連通分量,然后找出所有獨(dú)立的強(qiáng)連通分量(所謂獨(dú)立,就是該連通分量里面的點(diǎn)到外面的點(diǎn)沒(méi)有通路,當(dāng)然,連通分量外的點(diǎn)是可以有路到強(qiáng)連通分量?jī)?nèi)的點(diǎn)的),如果獨(dú)立的強(qiáng)連通分量的數(shù)目只有一個(gè),那么,就輸出這個(gè)強(qiáng)連通分量?jī)?nèi)解的個(gè)數(shù),否則輸出無(wú)解。
算法證明:
1:假設(shè)a和b都是最受歡迎的cow,那么,a歡迎b,而且b歡迎a,于是,a和b是屬于同一個(gè)連通分量?jī)?nèi)的點(diǎn),所有,問(wèn)題的解集構(gòu)成一個(gè)強(qiáng)連通分量。
2:如果某個(gè)強(qiáng)連通分量?jī)?nèi)的點(diǎn)a到強(qiáng)連通分量外的點(diǎn)b有通路,因?yàn)閎和a不是同一個(gè)強(qiáng)連通分量?jī)?nèi)的點(diǎn),所以b到a一定沒(méi)有通路,那么a不被b歡迎,于是a所在的連通分量一定不是解集的那個(gè)連通分量。
3:如果存在兩個(gè)獨(dú)立的強(qiáng)連通分量a和b,那么a內(nèi)的點(diǎn)和b內(nèi)的點(diǎn)一定不能互相到達(dá),那么,無(wú)論是a還是b都不是解集的那個(gè)連通分量,問(wèn)題保證無(wú)解。
4:如果圖非連通,那么,至少存在兩個(gè)獨(dú)立的連通分量,問(wèn)題一定無(wú)解。
第一次用前向星存儲(chǔ)圖,在不方便開(kāi)臨街矩陣和臨街表的情況下,用前向星存儲(chǔ)邊,再開(kāi)一個(gè)數(shù)組存儲(chǔ)開(kāi)始和結(jié)束的位置,很方便。
#include<iostream>
using namespace std;
int n,m;
struct e
{
int a,b;
}edge[50002];
int begin[10001],end[10001],ord[10001],belong[10001];//ord[]----按照dfs1逆序排列頂點(diǎn),belong[]-----判斷各點(diǎn)屬于哪個(gè)分量
int order=0,indx=0;
bool noout[10001],v[10001];//noout----判斷某強(qiáng)連通子圖是否出度為0.
int cmp1(const void *p,const void *q)
{
struct e *c=(e *)p;
struct e *d=(e *)q;
return c->a>d->a?1:-1;
}
int cmp2(const void *p,const void *q)
{
struct e *c=(e *)p;
struct e *d=(e *)q;
return c->b>d->b?1:-1;
}
int dfs1(int t)
{
if(v[t])
return 0;
v[t]=1;
int i;
for(i=begin[t];i<=end[t];i++)
{
dfs1(edge[i].b);
}
ord[order++]=t;
}
int dfs2(int t)
{
if(v[t])
return 0;
v[t]=1;
int i;
for(i=begin[t];i<=end[t];i++)
{
dfs2(edge[i].a);
}
belong[t]=indx;
return 0;
}
int main()
{
int i,j;
scanf("%d%d",&n,&m);
for(i=0;i<m;i++)
{
scanf("%d%d",&edge[i].a,&edge[i].b);
}
qsort(edge,m,sizeof(edge[0]),cmp1);
for(i=1;i<=n;i++)
{
begin[i]=1;
}
int pre=0;
for(i=0;i<=m;i++)
{
if(edge[i].a!=pre)
{
begin[edge[i].a]=i;
end[pre]=i-1;
pre=edge[i].a;
}
}//處理數(shù)組下標(biāo)
memset(v,0,sizeof(v));
for(i=1;i<=n;i++)
{
dfs1(i);
}
qsort(edge,m,sizeof(edge[0]),cmp2);
for(i=1;i<=n;i++)
{
begin[i]=1;
}
pre=0;
for(i=0;i<=m;i++)
{
if(edge[i].b!=pre)
{
begin[edge[i].b]=i;
end[pre]=i-1;
pre=edge[i].b;
}
}
memset(v,0,sizeof(v));
for(i=order-1;i>=0;i--)
{
if(!v[ord[i]])
{
indx++;
dfs2(ord[i]);
}
}
memset(noout,1,sizeof(noout));
for(i=0;i<m;i++)
{
if(belong[edge[i].a]!=belong[edge[i].b])
{
noout[belong[edge[i].a]]=0;
}
}
int sum=0,right;
for(i=1;i<=indx;i++)
{
if(noout[i])
{
sum++;
right=i;
}
}
if(sum==1)
{
sum=0;
for(i=1;i<=n;i++)
{
if(belong[i]==right)
sum++;
}
printf("%d\n",sum);
}
else
printf("0\n");
//system("pause");
return 0;
}
using namespace std;
int n,m;
struct e
{
int a,b;
}edge[50002];
int begin[10001],end[10001],ord[10001],belong[10001];//ord[]----按照dfs1逆序排列頂點(diǎn),belong[]-----判斷各點(diǎn)屬于哪個(gè)分量
int order=0,indx=0;
bool noout[10001],v[10001];//noout----判斷某強(qiáng)連通子圖是否出度為0.
int cmp1(const void *p,const void *q)
{
struct e *c=(e *)p;
struct e *d=(e *)q;
return c->a>d->a?1:-1;
}
int cmp2(const void *p,const void *q)
{
struct e *c=(e *)p;
struct e *d=(e *)q;
return c->b>d->b?1:-1;
}
int dfs1(int t)
{
if(v[t])
return 0;
v[t]=1;
int i;
for(i=begin[t];i<=end[t];i++)
{
dfs1(edge[i].b);
}
ord[order++]=t;
}
int dfs2(int t)
{
if(v[t])
return 0;
v[t]=1;
int i;
for(i=begin[t];i<=end[t];i++)
{
dfs2(edge[i].a);
}
belong[t]=indx;
return 0;
}
int main()
{
int i,j;
scanf("%d%d",&n,&m);
for(i=0;i<m;i++)
{
scanf("%d%d",&edge[i].a,&edge[i].b);
}
qsort(edge,m,sizeof(edge[0]),cmp1);
for(i=1;i<=n;i++)
{
begin[i]=1;
}
int pre=0;
for(i=0;i<=m;i++)
{
if(edge[i].a!=pre)
{
begin[edge[i].a]=i;
end[pre]=i-1;
pre=edge[i].a;
}
}//處理數(shù)組下標(biāo)
memset(v,0,sizeof(v));
for(i=1;i<=n;i++)
{
dfs1(i);
}
qsort(edge,m,sizeof(edge[0]),cmp2);
for(i=1;i<=n;i++)
{
begin[i]=1;
}
pre=0;
for(i=0;i<=m;i++)
{
if(edge[i].b!=pre)
{
begin[edge[i].b]=i;
end[pre]=i-1;
pre=edge[i].b;
}
}
memset(v,0,sizeof(v));
for(i=order-1;i>=0;i--)
{
if(!v[ord[i]])
{
indx++;
dfs2(ord[i]);
}
}
memset(noout,1,sizeof(noout));
for(i=0;i<m;i++)
{
if(belong[edge[i].a]!=belong[edge[i].b])
{
noout[belong[edge[i].a]]=0;
}
}
int sum=0,right;
for(i=1;i<=indx;i++)
{
if(noout[i])
{
sum++;
right=i;
}
}
if(sum==1)
{
sum=0;
for(i=1;i<=n;i++)
{
if(belong[i]==right)
sum++;
}
printf("%d\n",sum);
}
else
printf("0\n");
//system("pause");
return 0;
}
不介意我拿走吧?
到你這才知道要干啥.....
精辟的解釋?zhuān)梦?/div>