【洛谷题解/USACO题解】P2746 【USACO5.3】校园网Network of Schools

题目链接: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| PQ ∣ Q ∣ ≤ ∣ P ∣ |Q|\leq|P| QP。我们探究前者即可,后者的探究过程无非就是把前者的探究过程翻转。

对于 ∣ 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,QP>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=P1,Q=Q1,最少至少存在一个起点,那么根据讨论的第一种情况,当 ∣ P ∣ = 1 |P|=1 P=1 时,答案为 Q Q Q

开头提到,可能还存在 ∣ P ∣ ≥ ∣ Q ∣ |P| \geq |Q| PQ 的情况,证明过程就是 ∣ P ∣ ≤ ∣ Q ∣ |P|\leq |Q| PQ 翻转过来,因此答案就是起点数量和终点数量的更大值,即 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; 
}
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

oier_Asad.Chen

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

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

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

打赏作者

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

抵扣说明:

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

余额充值