题目链接:https://www.luogu.com.cn/problem/P2746
难度:提高+/省选-
涉及知识点:有向图的强联通分量
题意
在一个网络中,学校之间会存在一种协议,对于题目给定的一组学校 ( A , B ) (A,B) (A,B),当学校 A A A 得到一种新软件副本后,它必须立马传送给 B B B,这种关系是单向的。现在需要求解两个问题:
- 子问题 1:最先开始需要传送给多少个学校一个软件,才能让所有学校得到这个软件
- 子问题 2:再建立至少多少个协议关系可以使我给任意一个学校一个软件,就能让所有学校得到这个软件
分析与解决
首先观察可得这种关系可以建立一个有向图。
子问题 1
简单的。首先题目没有保证不出现环,所以我们可以联想到强连通分量。再深入地思考,就是对于一个入度为 0 的强连通分量,需要给一个软件;对于入度非 0 的强联通分量则可以由其他分量传递得到。
因此解决该问题就是把所有入度为 0 的强联通分量的数量统计一下。
子问题 2
该问题的本质是将整个图变成强联通分量,不难想到可能起点和终点的数量有数学关系。进一步考虑,设做了强连通分量后的图的起点集合为 P P P,终点集合为 Q Q Q,那么可能有两种情况, ∣ P ∣ ≤ ∣ Q ∣ |P|\leq|Q| ∣P∣≤∣Q∣ 或 ∣ Q ∣ ≤ ∣ P ∣ |Q|\leq|P| ∣Q∣≤∣P∣。我们探究前者即可,后者的探究过程无非就是把前者的探究过程翻转。
对于 ∣ P ∣ |P| ∣P∣,可能存在两种情况,我们着手讨论:
- ∣ P ∣ = 1 |P|=1 ∣P∣=1
对于该种情况,我们尝试画这样一个图:
在该图中,起点可以走到任意点,那么对于所有边上,如果存在中间点,也是一定可以走到。那么在这种情况下,如何保证所有终点也能达到其他点呢?向其他中间点连边?这显然不能到达高度小于该中间点的节点。如果向中间点连边,则还需要从中间点继续向深度更小的节点连边。
受此启发,最好的方法就是让每个终点向起点直接连边,节省向中间点连边的操作,共 ∣ Q ∣ |Q| ∣Q∣ 条,即可保证整个图成为强连通分量。
那可不可能小于 ∣ Q ∣ |Q| ∣Q∣ ?不可能。如果我少连一条边,那么这个未新连边的点就一定不能到达其他点了。
综上,当 ∣ P ∣ = 1 |P|=1 ∣P∣=1 时,子问题 2 的答案为 ∣ Q ∣ |Q| ∣Q∣。
- ∣ P ∣ > 1 , ∣ Q ∣ ≥ ∣ P ∣ > 1 |P|>1,|Q|\geq|P|>1 ∣P∣>1,∣Q∣≥∣P∣>1
尝试能否把这种情况转化为
∣
P
∣
=
1
|P|=1
∣P∣=1 的情况。设
∣
P
∣
=
k
|P|=k
∣P∣=k,则至少存在
k
k
k 对起点和终点,可以由起点到达终点。
如图示,存在两对,如何让这个图成为强联通分量呢,连 1 条边够吗?我们尝试所有连接情况后,发现总是会存在至少 1 个点是无法到达其他任意点的。那如果连 2 条边呢,比如下面这种情况:
我们发现从这 4 个点,都可以到达其他任意一个点。
我们设连边后起点减少 1 个,终点减少 1 个,即 ∣ P ′ ∣ = ∣ P ∣ − 1 , ∣ Q ′ ∣ = ∣ Q ∣ − 1 |P'|=|P|-1,|Q'|=|Q|-1 ∣P′∣=∣P∣−1,∣Q′∣=∣Q∣−1,最少至少存在一个起点,那么根据讨论的第一种情况,当 ∣ P ∣ = 1 |P|=1 ∣P∣=1 时,答案为 Q Q Q。
开头提到,可能还存在 ∣ P ∣ ≥ ∣ Q ∣ |P| \geq |Q| ∣P∣≥∣Q∣ 的情况,证明过程就是 ∣ P ∣ ≤ ∣ Q ∣ |P|\leq |Q| ∣P∣≤∣Q∣ 翻转过来,因此答案就是起点数量和终点数量的更大值,即 max ( ∣ P ∣ , ∣ Q ∣ ) \max(|P|,|Q|) max(∣P∣,∣Q∣)。
AC代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 110, M = 10010;
int n;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], timestamp;
int id[N], scc_cnt;
int stk[N], top;
bool in_stk[N];
int din[N], dout[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
return;
}
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 y;
do
{
y = stk[top--];
in_stk[y] = false;
id[y] = scc_cnt;
} while (y != u);
}
return;
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d", &n);
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; 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++;
if (!dout[i]) b++;
}
printf("%d\n", a);
if (scc_cnt == 1) puts("0");
else printf("%d\n", max(a, b));
return 0;
}