学校网络(有向图的强连通分量)

题目描述

一些学校连接在一个计算机网络上,学校之间存在软件支援协议,每个学校都有它应支援的学校名单(学校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;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lwz_159

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值