题解:洛谷 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

【核心思想】

  1. 问题分析:给定 \(N\) 个节点、\(N-1\) 条边的有向树,求是否存在一个节点 \(i\) 使得所有其他节点都能到达 \(i\)(即"汇点"或"根"),输出最小的这样的 \(i\)。本质上是传递闭包 + 全源可达性判定问题:先计算所有节点对的可达性,再检查每个节点是否被所有其他节点可达。

  2. 算法选择

    • Floyd-Warshall 传递闭包\(N \leq 100\),用 \(O(N^3)\) 的 Floyd 计算所有点对可达性
    • 汇点扫描:对每个节点 \(i\),检查是否所有 \(j\) 满足 \(g[j][i] = 1\)
  3. 关键步骤

    • 读取数据:读入 \(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\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N^3)\),Floyd 传递闭包
    • 空间复杂度:\(O(N^2)\),邻接矩阵
  5. 传递闭包的核心思想

    • 树的特殊性\(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
posted @ 2026-08-17 09:45  团爸讲算法  阅读(12)  评论(0)    收藏  举报