/*poj1236Network of Schools
题意:网络中有一些学校,每个学校可以分发软件给其他学校。可以向哪个分发取决于他们各自维护的一个清单。有两个问题,:
1:至少要copy多少份新软件给那些学校,才能使得每个学校都能得到。
2:要在所有的学校的清单里面至少一共增加几项才能使得 把软件给任意一个学校,所有的学校都能收得到。
思路:问题一:要使每个学校都能收到,就是计算图里面一共有多少入度是的点,这个好理解。
问题二:先学习下计算强连通分支的算法——Tarjan, 然后缩点,把每一块看成一个点,出来一个新的有向图。然后计算出度为的入度为的点各有多少,大的那个就是答案。值得注意的是,当只有一个强连通分支的时候,就是缩点后只有一个点,虽然入度出度为的都有一个,但是实际上不需要增加清单的项了,所以答案是1,0;
*/
View Code
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 111
int head[MAXN*MAXN],next[MAXN*MAXN],u[MAXN*MAXN],v[MAXN*MAXN];
int DFN[MAXN],LOW[MAXN],instack[MAXN],Stap[MAXN],ID[MAXN],In[MAXN],Out[MAXN];
int Dindex,Stop,Bcnt;
void tarjan(int i)
{
int j;
DFN[i]=LOW[i]=++Dindex;//定义DFN(u)为节点u搜索的次序编号,LOW(u)为u或u的子树能够追溯到的最早的栈中节点的次序
instack[i]=true;//i在栈中
Stap[++Stop]=i;//模拟栈
for(int e=head[i];e>=0;e=next[e])
{
j=v[e];
if(!DFN[j])//若这条边的终点没访问过则取查询该点
{
tarjan(j);
if(LOW[j]<LOW[i])LOW[i]=LOW[j];//子树能追溯到的时间戳小则更新父亲追溯到的时间戳
}
else if(instack[j]&&DFN[j]<LOW[i])
LOW[i]=DFN[j];
}
if(DFN[i]==LOW[i])//找到一个连通分量把这个连通分量退栈
{
Bcnt++;
do
{
j=Stap[Stop--];
instack[j]=false;
ID[j]=Bcnt;//缩点后强连通区域个数
}while(j!=i);
}
}
int n,m,num;
void pro()
{
Dindex=Stop=Bcnt=0;
memset(DFN,0,sizeof(DFN));
memset(ID,-1,sizeof(ID));
for(int i=1;i<=n;i++)
{
if(!DFN[i])tarjan(i);
}
}
int main()
{
while(scanf("%d",&n)==1)
{
num=0;
for(int i=1;i<=n;i++)head[i]=-1,next[i]=-1;
for(int i=1;i<=n;i++)
{
while(scanf("%d",&m))
{
if(!m)break;
u[num]=i,v[num]=m;
next[num]=head[i];
head[i]=num;
num++;
}
}
pro();
int in0=0,out0=0,Num=0;
memset(In,0,sizeof(In));
memset(Out,0,sizeof(Out));
for(int i=0;i<num;i++)
{
int s=ID[u[i]];
int t=ID[v[i]];
if(s!=t)
{
In[t]=1;
Out[s]=1;
}
}
for(int i=1;i<=Bcnt;i++)
{
if(!In[i])in0++;//计算入度为的点的个数
if(!Out[i])out0++;//计算出度为的点的个数
}
if(Bcnt==1)printf("1\n0\n");//连通分量为特殊情况
else printf("%d\n%d\n",in0,max(in0,out0));
}
}