题解:洛谷 P1700 Milk Factory
【题目来源】
洛谷:P1700 [USACO19OPEN] Milk Factory B - 洛谷
【题目描述】
牛奶生意正红红火火!Farmer John 的牛奶加工厂内有 \(N\) 个加工站,编号为 \(1\dots N\)(\(1\le N\le 100\)),以及 \(N−1\) 条通道,每条连接某两个加工站。(通道建设很昂贵,所以 Farmer John 选择使用了最小数量的通道,使得从每个加工站出发都可以到达所有其他加工站)。
为了创新和提升效率,Farmer John 在每条通道上安装了传送带。不幸的是,当他意识到传送带是单向的已经太晚了,现在每条通道只能沿着一个方向通行了!所以现在的情况不再是从每个加工站出发都能够到达其他加工站了。
然而,Farmer John 认为事情可能还不算完全失败,只要至少还存在一个加工站 \(i\) 满足从其他每个加工站出发都可以到达加工站 \(i\)。注意从其他任意一个加工站 \(j\) 前往加工站 \(i\) 可能会经过 \(i\) 和 \(j\) 之间的一些中间站点。请帮助 Farmer John 求出是否存在这样的加工站 \(i\)。
【输入】
输入的第一行包含一个整数 \(N\),为加工站的数量。以下 \(N−1\) 行每行包含两个空格分隔的整数 \(a_i\) 和 \(b_i\),满足 \(1\le a_i,b_i\le N\) 以及 \(a_i\neq b_i\)。这表示有一条从加工站 \(a_i\) 向加工站 \(b_i\) 移动的传送带,仅允许沿从 \(a_i\) 到 \(b_i\) 的方向移动。
【输出】
如果存在加工站 \(i\) 满足可以从任意其他加工站出发都可以到达加工站 \(i\),输出最小的满足条件的 \(i\)。否则,输出 \(−1\)。
【输入样例】
3
1 2
3 2
【输出样例】
2
【核心思想】
-
问题分析:给定 \(N\) 个节点、\(N-1\) 条边的有向树,求是否存在一个节点 \(i\) 使得所有其他节点都能到达 \(i\)(即"汇点"或"根"),输出最小的这样的 \(i\)。本质上是传递闭包 + 全源可达性判定问题:先计算所有节点对的可达性,再检查每个节点是否被所有其他节点可达。
-
算法选择:
- Floyd-Warshall 传递闭包:\(N \leq 100\),用 \(O(N^3)\) 的 Floyd 计算所有点对可达性
- 汇点扫描:对每个节点 \(i\),检查是否所有 \(j\) 满足 \(g[j][i] = 1\)
-
关键步骤:
- 读取数据:读入 \(N\) 和 \(N-1\) 条有向边
- 初始化邻接矩阵:\(g[i][i] = 1\),\(g[a][b] = 1\)(有向边 \(a \to b\))
- Floyd 传递闭包:
- 枚举中转点 \(k = 1\) 到 \(N\)
- 枚举起点 \(i = 1\) 到 \(N\)
- 枚举终点 \(j = 1\) 到 \(N\)
- 若 \(g[i][k] = 1\) 且 \(g[k][j] = 1\),则 \(g[i][j] = 1\)
- 查找汇点(\(i = 1\) 到 \(N\)):
- 检查是否所有 \(j\) 满足 \(g[j][i] = 1\)
- 若满足,输出 \(i\) 并结束
- 若未找到,输出 \(-1\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N^3)\),Floyd 传递闭包
- 空间复杂度:\(O(N^2)\),邻接矩阵
-
传递闭包的核心思想:
- 树的特殊性:\(N-1\) 条边连接 \(N\) 个节点,底层是无向树,但有向化后可能存在唯一的"汇点"
- Floyd 的布尔版本:用 \(0/1\) 表示可达性,通过逻辑与(
g[i][k] && g[k][j])更新传递闭包 - 汇点的判定:节点 \(i\) 是汇点当且仅当所有节点(包括自己)都能到达 \(i\),即 \(g[j][i] = 1\) 对所有 \(j\) 成立
- 最小汇点:从小到大扫描,第一个满足条件的即为答案
- 适用于有向图可达性、传递闭包、汇点查找类问题
【解题思路】

【算法标签】
普及- #Floyd
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 105; // 最大节点数
int n; // 节点数量
int g[N][N]; // 邻接矩阵,g[i][j]=1表示i可以到达j
int a, b; // 临时变量,用于输入边
int main()
{
// 输入节点数量
cin >> n;
// 初始化邻接矩阵:每个节点可以到达自己
for (int i = 1; i <= n; i++)
{
g[i][i] = 1;
}
// 输入n-1条边(树结构)
for (int i = 1; i < n; i++)
{
cin >> a >> b;
g[a][b] = 1; // 标记a可以到达b
}
// 使用Floyd-Warshall算法计算传递闭包
// 计算所有节点对的可达性关系
for (int k = 1; k <= n; k++)
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
// 如果i可以到达k,且k可以到达j,则i可以到达j
if (g[i][k] == 1 && g[k][j] == 1)
{
g[i][j] = 1;
}
}
}
}
// 查找根节点:所有其他节点都可以到达的节点
for (int i = 1; i <= n; i++)
{
bool flag = true; // 标记当前节点i是否是根节点
// 检查是否所有节点j都可以到达节点i
for (int j = 1; j <= n; j++)
{
// 如果存在节点j不能到达i,则i不是根节点
if (g[j][i] == 0)
{
flag = false;
break; // 提前退出,优化性能
}
}
// 如果找到根节点,输出并结束程序
if (flag)
{
cout << i << endl;
return 0;
}
}
// 如果没有找到根节点,输出-1
cout << -1 << endl;
return 0;
}
【运行结果】
3
1 2
3 2
2
浙公网安备 33010602011771号