题目描述
一些学校连接在一个计算机网络上,学校之间存在软件支援协议,每个学校都有它应支援的学校名单(学校A支援学校B,并不表示学校B一定要支援学校A)。
当某校获得一个新软件时,无论是直接获得还是通过网络获得,该校都应立即将这个软件通过网络传送给它应支援的学校。
因此,一个新软件若想让所有学校都能使用,只需将其提供给一些学校即可。
现在请问最少需要将一个新软件直接提供给多少个学校,才能使软件能够通过网络被传送到所有学校?
最少需要添加几条新的支援关系,使得将一个新软件提供给任何一个学校,其他所有学校就都可以通过网络获得该软件?
输入格式
第1行包含整数N,表示学校数量。
第2…N+1行,每行包含一个或多个整数,第i+1行表示学校 i 应该支援的学校名单,每行最后都有一个0表示名单结束(只有一个0即表示该学校没有需要支援的学校)。
输出格式
输出两个问题的结果,每个结果占一行。
数据范围
2≤N≤100
输入样例
5
2 4 3 0
4 5 0
0
0
1 0
输出样例
1
2
题目分析
因为图中存在很多环,我们无法清楚地分析出整个图的拓扑关系。因此我们可以考虑将图中的强连通分量缩成一个点,从而将题中的图转化为一个有向无环图,这样就可以清楚地分析出图中的拓扑关系了。
注:因为每一个强连通分量中的学校一定是相互应援的,因此只要我们将一个新软件提供给这个强连通分量中的任意一点,该强连通分量中的所有其它点都必然会得到该新软件,因此该题采用缩点的方法是可行的。
然后我们先来分析第一个问题:最少需要将一个新软件直接提供给多少个学校,才能使软件能够通过网络被传送到所有学校?
解:因为得到的新图是一个拓扑图,因此如果要想将新软件传送到所有学校,我们只需要将新软件给到图中所有入度为0的点(图中所有起点)就行了。
问题二:最少需要添加几条新的支援关系,使得将一个新软件提供给任何一个学校,其他所有学校就都可以通过网络获得该软件?
解:1.当新图只有一个点的时候(整个图为一个强连通分量),不需要再添加边了。答案为0。
设:s为入度为0的点集(起点),t为出度为0的点集(终点)。
2.当t>=s时。
1)s==1,此时我们只需要让t中的所有点连一条到s的边即可。此时答案为t。
2)t>=s>1,则此时至少存在两个起点s1,s2和两个终点t1,t2。我们可以假设,s1至少能够走到t1,s2至少能够走到t2。那么我们可以从t1连一条边到s2,这样图的起点和终点都能减少一个。
通过这样的方式连s-1条边,这样得到的新图中s’=1,t’=t-s+1。此时问题转化为了情况1),要想得到答案我们就需要再连t’条边。最终的答案为:ans = s-1+t' = s-1+t-s+1 = t
。
因此t>=s时,答案为t。
3.s>=t时。
这种情况与t>=s的情况是对称的,这里不展开说了,这种情况的答案为s。
综上,问题二的答案即为:max(s,t);
代码如下
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <map>
#include <queue>
#include <vector>
#include <set>
#include <algorithm>
#define LL long long
#define ULL unsigned long long
#define PII pair<int,int>
#define x first
#define y second
using namespace std;
const int N=1e2+5,M=1e3+5,INF=0x3f3f3f3f;
int n;
vector<int> h[N];
int num[N],low[N],idx;
int stk[N],top;
bool st[N];
int scc[N],cnt;
int in[N],out[N];
void tarjan(int u) //求强连通分量(模板)
{
num[u]=low[u]=++idx;
stk[++top]=u; st[u]=true;
for(int i=0;i<h[u].size();i++)
{
int v=h[u][i];
if(!num[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(st[v]) low[u]=min(low[u],num[v]);
}
if(num[u]==low[u])
{
++cnt;
int x;
do{
x=stk[top--];
st[x]=false;
scc[x]=cnt;
}while(u!=x);
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) //建图
{
int x;
while(scanf("%d",&x),x) h[i].push_back(x);
}
for(int i=1;i<=n;i++) //求强连通分量
if(!num[i]) tarjan(i);
for(int i=1;i<=n;i++) //缩点
for(int j=0;j<h[i].size();j++)
{
int v=h[i][j];
int a=scc[i],b=scc[v];
if(a!=b) //若a和b不在同一个强连通分量内,则a和b不为同一个缩点
{
in[b]++; //更新两点的度数
out[a]++;
}
}
int a=0,b=0;
for(int i=1;i<=cnt;i++) //统计新图中入度和出度为0的点
{
if(!in[i]) a++;
if(!out[i]) b++;
}
printf("%d\n",a); //问题一
if(cnt==1) puts("0"); //问题二
else printf("%d\n",max(a,b));
return 0;
}