AcWing 367. 学校网络


题目传送门

一、题目分析

\(Tarjan\) 缩点将原图转化成 \(DAG\),统计每个强连通分量的出度入度,起点数量为 \(src\),终点数量为 \(des\)。对于一个强连通分量,其中只要有一所学校获得新软件那么整个分量都能获得。

问题一
结论:只要把软件给所有起点即可,答案为起点个数 \(src\)

证明:
所有起点都无法由别的点到达,因此每个起点必须分配一个软件,而对于其他所有点,一定存在前驱,一定能由某一个起点走到,也就是说从所有起点出发,能遍历整个图。因此只需要给所有起点各一个软件即可。

问题二
结论:若 scc_cnt=1(只有一个强连通分量),则不需要连新的边,答案为 \(0\)
scc_cnt>1,则答案为 \(max(src,des)\)

证明(\(yxc\)讲解总结)
结论 \(1\) 正确性显然,下面证明结论 \(2\)

设缩点后的 \(DAG\) 中,起点(入度为 \(0\))的集合为 \(P\),终点(出度为 \(0\))的集合为 \(Q\)。分以下两种情况讨论:
\(|P|≤|Q|\)

① 若 \(|P|=1\),则只有一个起点,并且这个起点能走到所有点,只要将每一个终点都向这个起点连一条边,那么对于图中任意一点,都可以到达所有点,新加的边数为 \(|Q|\)

② 若 \(|P|≥2\),则 \(|Q|≥|P|≥2\),此时至少存在\(2\)个起点 \(p1,p2\)\(2\) 个终点 \(q1,q2\),满足 \(p1\) 能走到 \(q1\)\(p2\) 能走到 \(q2\)。(反证法:如果不存在两个起点能走到不同的终点,则所有的起点一定只能走到同一个终点,而终点至少有两个,发生矛盾,假设不成立)。如下图:

那么我们可以从 \(q1\)\(p2\) 新连一条边,那么此时起点和终点的个数都会减少一个(\(p2\) 不再是起点,\(q1\) 不再是终点),因此只要以这种方式,连接新边 \(|P|?1\) 条,则 \(|P′|=1\),而 \(|Q′|=|Q|?(|P|?1)\) ,由 ① 得,当 \(|P′|=1\) 时,需要再连 \(|Q′|\) 条新边,那么总添加的新边数量为 \(|P|?1+|Q|?(|P|?1)=|Q|\)

\(|Q|≤|P|\)
与情况 \(1\) 对称,此时答案为 \(|P|\)

综上所述,scc_cnt>1 时,问题二的答案为 \(max(|P|,|Q|)\)\(max(src,des)\)

二、实现代码

#include 
using namespace std;

const int N = 110, M = 10010;

int n;
int dfn[N], low[N], timestamp;
int stk[N], top;
bool in_stk[N];
int id[N], scc_cnt;
int din[N], dout[N];

//邻接表
int e[M], h[N], idx, ne[M];
void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

// Tarjan算法求强连通分量
void tarjan(int u) {
    dfn[u] = low[u] = ++timestamp;
    stk[++top] = u;
    in_stk[u] = true;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (!dfn[j]) {
            tarjan(j);
            low[u] = min(low[u], low[j]);
        } else if (in_stk[j])
            low[u] = min(low[u], low[j]);
    }
    if (dfn[u] == low[u]) {
        ++scc_cnt;
        int x;
        do {
            x = stk[top--];
            in_stk[x] = false;
            id[x] = scc_cnt;
        } while (x != u);
    }
}

int main() {
    cin >> n;
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i++) {
        int t;
        while (cin >> t, t) add(i, t);
    }

    for (int i = 1; i <= n; i++)
        if (!dfn[i]) tarjan(i);

    for (int i = 1; i <= n; i++)
        for (int j = h[i]; j != -1; j = ne[j]) {
            int k = e[j];
            int a = id[i], b = id[k];
            if (a != b) {
                dout[a]++;
                din[b]++;
            }
        }

    int a = 0, b = 0;
    for (int i = 1; i <= scc_cnt; i++) {
        if (!din[i]) a++;  //入度为0的强连通块
        if (!dout[i]) b++; //出度为0的强连通块
    }

    printf("%d\n", a);
    if (scc_cnt == 1) //如果只有一个强连通块,不用连边
        puts("0");
    else
        printf("%d\n", max(a, b)); //输出入度为0与出度为0的连通块最大值
    return 0;
}

相关