有一些学校连接到一个计算机网络,这些学校之间达成了一个协议:每个学校维护着一个学校列表,它向学校列表中的学校发布软件。注意,如果学校B在学校A的列表中,则A不一定在B的列表中。
任务A:计算为使得每个学校都能通过网络收到软件,至少需要准备多少份软件拷贝。
任务B:考虑一个更长远的任务,想确保给任意一个学校发放一个新的软件拷贝。该软件拷贝能发布到网络中的每个学校,为了达到这个目标,必须在列表中增加新成员。计算所需添加新成员的最小数目。
首先,缩点是很有必要的。
新生成的有向图是一个有向无环图。然后对于任务A,很容易想到,如果一些点有一个公共祖先,那么祖先有了软件,他们也都有了,所以求所有不同的公共祖先个数即可,而这些公共祖先的特征是入度为0。
任务B是添加尽量少的边,使图完全连通。图中有一片森林,有一些祖先结点和叶子结点,祖先结点特征是入度为0,叶子结点的特征是出度为0。很显然,只需要把一颗树的叶子结点轮流连接到相邻树的祖先结点上就能达到这个目标,但是当祖先结点多于叶子结点时,这是不够的,而此时,完全可以把所有的祖先结点相连而不去管叶子结点,最后B任务的答案就是祖先结点和叶子结点数量的较大值。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
#define CLR(a,b) memset(a,b,sizeof(a))
#define Min(a,b) a>b?b:a
using namespace std;
const int I=150;
int Bcnt,Dindex;
int instack[I],path[I][I],in[I],out[I],DFN[I],LOW[I],Belong[I];
stack<int> Stack;
struct node
{
int to;
node *next;
};
node *edge[I];
void tarjan(int u)
{
DFN[u]=LOW[u]=++Dindex;
Stack.push(u);
instack[u]=1;
for(node *e=edge[u];e;e=e->next)
{
int v=e->to;
if(!DFN[v])
{
tarjan(v);
LOW[u]=Min(LOW[u],LOW[v]);
}
else if(instack[v])
LOW[u]=Min(LOW[u],DFN[v]);
}
if(DFN[u]==LOW[u])
{
Bcnt++;
int j;
do
{
j=Stack.top();
instack[j]=0;
Stack.pop();
Belong[j]=Bcnt;
}while(u!=j);
}
}
void solve(int n)
{
CLR(DFN,0);
Bcnt=Dindex=0;
for(int i=1;i<=n;i++)
if(!DFN[i])
tarjan(i);
}
int main()
{
CLR(in,0);
CLR(out,0);
CLR(path,0);
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int r;
while(scanf("%d",&r) && r!=0)
{
node *temp=new node;
temp->to=r;
temp->next=edge[i];
edge[i]=temp;
}
}
solve(n);
for(int i=1;i<=n;i++)
{
for(node *e=edge[i];e;e=e->next)
{
int j=e->to;
if(Belong[i]!=Belong[j] && path[Belong[i]][Belong[j]]==0)
{
in[Belong[j]]++;
out[Belong[i]]++;
path[Belong[i]][Belong[j]]==1;
}
}
}
int ansin(0),ansout(0);
for(int i=1;i<=Bcnt;i++)
{
if(in[i]==0) ansin++;
else if(out[i]==0) ansout++;
}
if(Bcnt==1) ansout=0;
else ansout=ansout>ansin?ansout:ansin;
cout<<ansin<<endl;
cout<<ansout<<endl;
return 0;
}