题面
【题目描述】
有一些学校连到了一个电脑网络中。这些学校达成了一个协议:每个学校保存一个其他学校名字的列表,列出它发送软件能到达的学校(即接收学校)。注意:如果B在A学校的发送列表中,那么A不一定在B学校的列表中。你需要写一个程序,计算最小的学校数量,这些学校接收新软件后,能让所有的学校在网络内能接收到这个软件(子任务一)。作为一个额外的任务,我们想要保证通过传输新软件到一个任意的学校能使这个软件能在网络中送达到所有的学校。为了达到这个目标,我们可能需要拓展新成员到学校的接收列表。计算这个需要拓展的最小数量,使得无论我们发送新软件到哪个学校,它都可以到达任意一个学校(子任务二)。一个拓展意味着增加一个新成员到某一个学校的接收列表中。
【输入】
第一行包括一个整数N:网络中的学校数量(2≤N≤100)。1~N每个数字分别代表一个学校。每个接下来的N行描述了一个接收者列表。第i+1行包括了学校i的接收列表。每个列表以0结束。一个空列表在一行中只有一个单独的0.
【输出】
你的程序需要写两行作为标准输出。第一行需要包括一个正整数:子任务一的答案。第二行需要包括子任务二的答案。
【样例输入】
5
2 4 3 0
4 5 0
0
0
1 0
【样例输出】
1
2
算法分析
强连通分量缩点。
先解决第一个问题:计算最小的学校数量,这些学校接收新软件后,能让所有的学校在网络内能接收到这个软件。
在同一个强连通分量里面,将软件给任何一个学校,强连通分量里的所有学校都可以收到,考虑进行缩点。
假如,缩点后有
5
5
5个强连通分量,关系如下图:
大家仔细观察,对于强连通分量
4
4
4而言,
5
5
5是可以发送给它的,同样的
1
1
1也可以发送给
2
2
2…因此对于强连通分量
4
4
4,
2
2
2所包含的学校是不需要发送软件给它们,那么发送给哪些强连通分量呢?
入度为
0
0
0的强连通分量。为什么呢?
因为入度为
0
0
0,没有其他强连通分量能够发送过来。入度不为
0
0
0,肯定可以从其他强连通分量那里接收。因此,入度为0的强连通分量的个数就是第一问的答案。
接下来,我们考虑第二个问题:拓展新成员到学校的接收列表。计算这个需要拓展的最小数量,使得无论我们发送新软件到哪个学校,它都可以到达任意一个学校。
结合第一问,入度为
0
0
0的强连通分量是不能接受到软件的,那么我们可以添加一条边,指向入度为
0
0
0的,设入度为
0
0
0的强连通分量数量为
X
X
X,则至少添加
X
X
X条边。
这样就可以了吗?
如果出现出度为
0
0
0的强连通分量,也会出现问题,软件收到了发不出去,我们可以添加一条指向其他强连通分量的边,设出度为
0
0
0的强连通分量数量为
Y
Y
Y,则至少添加
Y
Y
Y条边。
那么,最后的答案就是
X
+
Y
?
X+Y?
X+Y?
入度为
0
0
0的强连通分量需要一条入边,而出度为
0
0
0的强连通分量需要一条出边,那么我们就刚好可以添加一条边,从出度为
0
0
0的指向入度为
0
0
0的,这样,添加的边才能最少。
因此,最终答案为
m
a
x
(
X
,
Y
)
max(X,Y)
max(X,Y)。
参考程序
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stack>
using namespace std;
stack <int >s;
int n,timee,low[110],dfn[110],now[110];
int a[110][110],chu[110],ru[110],b[110][110];
int scc[110],zh;
int fist[1010],to[1010],nex[1010],t;
void add(int x,int y)
{
to[++t]=y;
nex[t]=fist[x];
fist[x]=t;
return;
}
void tarjin(int u)
{
low[u]=dfn[u]=++timee;
s.push(u);
now[u]=1;
int v;
for(int i=fist[u];i!=-1;i=nex[i])
{
v=to[i];
if(dfn[v]==0)
{
tarjin(v);
low[u]=min(low[u],low[v]);
}
else if(now[v])
{
low[u]=min(low[u],dfn[v]);
}
}
if(dfn[u]==low[u])
{
zh++;
do
{
v=s.top();
s.pop();
scc[v]=zh;//缩点
now[v]=0;
}while(u!=v);
}
return ;
}
int main()
{
scanf("%d",&n);
int x;
for(int i=1;i<=n;i++)
fist[i]=-1;
for(int i=1;i<=n;i++)
{
while(1)
{
scanf("%d",&x);
if(x==0) break;
a[i][x]=1;
add(i,x);
}
}
for(int i=1;i<=n;i++)
if(dfn[i]==0) tarjin(i);
int ans=0,ans2=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(a[i][j]) b[scc[i]][scc[j]]=1;//缩点建立新图
for(int i=1;i<=zh;i++)
for(int j=1;j<=zh;j++)
{
if(i==j) continue;
if(b[i][j])
{
chu[i]++; //出度
ru[j]++; //入度
}
}
for(int i=1;i<=zh;i++)
{
if(chu[i]==0) ans++;
if(ru[i]==0) ans2++;
}
if(zh==1) printf("1\n0\n");
else
printf("%d\n%d\n",ans2,max(ans,ans2));
return 0;
}