AcWing 367 学校网络

题目描述:
一些学校连接在一个计算机网络上,学校之间存在软件支援协议,每个学校都有它应支援的学校名单(学校 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
分析:
本题唯一的难点在于结论的推导,推导出结论后直接调用Tarjan算法的模板即可解决问题。
首先,至少要将新软件提供给多少个学校,才能保证所有的学校都获得软件。
对于普通的DAG而言,只需要将新软件提供给所有入度为0的节点,就可以保证所有的学校都获得新的软件。

1
3
2
4
5
6

如图所示,入度为0的节点有1和2,这意味着没有任何节点可以到达1和2节点,所以至少要提供2个新软件才能保证1和2都获得软件;同时DAG中任意一个入度不为0的节点都可以从某个入度为0的节点出发遍历到,正如树中的根节点一定可以达到树中的任意内部节点一样,所以只要将新软件提供给入度为0的节点,图中所有的节点都将获得新软件。
对于一般图而言,将原图采用Tarjan算法缩点后会形成新的DAG,只需要提供缩点后入度为0的节点的个数的新软件,就可以让所有的学校都获得软件。因为缩点后的SCC内部节点都是彼此可达的,SCC中一个节点能够获取软件。其他节点就也都可以获取到软件。
总而言之,至少需要提供缩点后入度为0的节点个数个新软件,就能够使得软件被传递到所有的学校
第二个问题,至少需要添加几条支援关系,才能够使得将新软件提供给任意一个学校,所有学校都可以获取到该软件。
SCC中的节点是彼此可达的,因此我们只需要考虑缩点后的DAG中要添加几条有向边,才能够让图变成一个完整的SCC。

1
3
2
4
5
6
7

在之前的分析中,我们知道,任意一个出度为0的节点都可以通过某入度为0的节点到达,后面我们将入度为0的节点称为起点,出度为0的节点称为终点。也就是说,每一个终点,都可以由某个起点到达,所以只需要连接每个终点到他们对应的起点,那么起点能够到达的位置,该终点也可以到达,比如连接5到1,那么1原本可以到达的节点,5也都可以到达了,同时,由于5到1的连接,原图中的起点和终点都少了一个,如果再连接7到2,图中的起点和终点又都少了一个。现在图中只剩下一个出度为0的节点也就是6,将6连接到1,此时的图就变成了一个SCC,也就是出度和入度为0的节点消失了。
因此,我们可以从入度和出度的角度来看待该问题,假设图的初始状态有m个入度为0的节点和n个出度为0的节点,我们的目标是添加边将其转化为出度或者入度等于0节点的个数为0的一个强连通图。正如上面的例子那样,我们增加一条边,最多可以同时减去一个起点和一个终点,如果起点个数m < n,那么我们前m次消掉了m个起点和终点,后n - m次消掉了n - m个终点,也就是至少n次操作才能将原图转换为不存在出度或者入度是0节点的图;同理m > n时,至少要通过m次加边操作才能够实现目标。
不含有出度为0和入度为0的节点只是强连通图的一个充分条件而已,所以至少要max(m,n)次加边操作才能实现目的。
下面证明max(m,n)次操作可以将原图转化为强连通图,m > n,时,我们每次连接一条终点到起点的边就可以消去一个入度为0的节点以及一个出度为0的节点,这里说的入度为0或者出度为0节点的个数即使是在加边缩点后依旧是减少的。所以n次操作之后只会留下m - n个起点,从原先任意一个终点连m - n条边到达剩下的起点,之后原图就会转化为一个强连通图;m < n的情况也是类似,所以,通过max(m, n)次加边操作可以实现目标。
综上所述,至少需要加max(m, n)条支援关系,才能够使得将新软件提供给任意一个学校,所有学校都可以获取到该软件。
有了上面的结论,我们只需要对原图做一遍Tarjan算法,统计下缩点后入度或者出度为0节点的个数即可得出答案。要注意的是,如果原图本身就是一个强连通图,也就是SCC数目为1时,不需要添加任何支援关系,都可以使得将新软件提供给任意一个学校,所有学校都可以获取到该软件。
本题总的代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 105, M = 10005;
int idx,h[N],ne[M],e[M];
int stk[N],id[N], dtime[N], low[N];
int din[N], dout[N];
int scc_cnt = 0, top = 0, t = 0;
bool in_stk[N];
void add(int a,int b) {
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
void tarjan(int u) {
    dtime[u] = low[u] = ++t;
    stk[++top] = u, in_stk[u] = true;
    for(int i = h[u];~i;i = ne[i]) {
        int j = e[i];
        if (!dtime[j]) {
            tarjan(j);
            low[u] = min(low[u], low[j]);
        } else if (in_stk[j]) {
            low[u] = min(low[u], dtime[j]);
        }
    }
    if (low[u] == dtime[u]) {
        scc_cnt++;
        int k;
        do {
            k = stk[top--];
            in_stk[k] = false;
            id[k] = scc_cnt;
        } while(k != u);
    }
}
int main() {
    int n, x;
    cin>>n;
    memset(h, -1, sizeof h);
    for(int i = 1;i <= n;i++) {
        while(cin>>x && x) {
            add(i, x);
        }
    }
    for(int i = 1;i <= n;i++) {
        if(!dtime[i]) {
            tarjan(i);
        }
    }
    for(int i = 1;i <= n;i++) {
        int a = id[i];
        for(int j = h[i];~j;j = ne[j]) {
            int k = e[j], b = id[k];
            if(a != b) {
                din[b]++;
                dout[a]++;
            }
        }
    }
    int ans1 = 0,ans2 = 0;
    for(int i = 1;i <= scc_cnt;i++) {
        if(!din[i]) ans1++;
        if(!dout[i])    ans2++;
    }
    cout<<ans1<<endl;
    if(scc_cnt == 1)    cout<<"0"<<endl;
    else    cout<<max(ans1, ans2)<<endl;
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值