题解:洛谷 P1547 Out of Hay

【题目来源】

洛谷:P1547 [USACO05MAR] Out of Hay S - 洛谷

【题目描述】

Bessie 计划调查 \(N\)\(2 \leq N \leq 2\,000\))个农场的干草情况,它从 \(1\) 号农场出发。农场之间总共有 \(M\)\(1 \leq M \leq 10^4\))条双向道路,所有道路的总长度不超过 \(10^9\)。有些农场之间存在着多条道路,所有的农场之间都是连通的。

Bessie 希望计算出该图中最小生成树中的最长边的长度。

【输入】

第一行两个整数 \(N,M\)

接下来 \(M\) 行,每行三个用空格隔开的整数 \(A_i,B_i,L_i\),表示 \(A_i,B_i\) 之间有一条道路,长度为 \(L_i\)

【输出】

一个整数,表示最小生成树中的最长边的长度。

【输入样例】

3 3
1 2 23
2 3 1000
1 3 43

【输出样例】

43

【核心思想】

  1. 问题分析:给定 \(N\) 个农场、\(M\) 条双向道路的无向连通图,求最小生成树(MST)中的最长边长度。这是一个最小生成树 + 边权追踪问题,关键在于利用 Kruskal 算法按边权从小到大选边,自然保证最后加入的边即为 MST 中的最大边。

  2. 算法选择

    • Kruskal 算法:按边权升序排序,用并查集维护连通性,每次选择不形成环的最小边
    • 最大边追踪:在 Kruskal 过程中,每次成功加入生成树时更新 maxn = max(maxn, w),最终 maxn 即为 MST 的最长边
  3. 关键步骤

    • 读取输入:读入 \(N, M\)\(M\) 条边 \((A_i, B_i, L_i)\)
    • 边排序:按边权 \(w\) 从小到大排序
    • 并查集初始化\(p[i] = i\)\(i\)\(1\)\(N\)
    • Kruskal 选边(遍历排序后的边):
      • 查找 \(a = find(A_i)\)\(b = find(B_i)\)
      • \(a \neq b\)(不连通):
        • 合并:\(p[a] = b\)
        • 更新最大边权:maxn = max(maxn, w)
    • 输出 maxn
  4. 时间/空间复杂度

    • 时间复杂度:\(O(M \log M + M \cdot \alpha(N))\),排序 \(O(M \log M)\),并查集操作近似 \(O(1)\)
    • 空间复杂度:\(O(N + M)\),并查集 + 边数组
  5. Kruskal 的核心思想

    • 贪心选边:按边权从小到大依次选择,若该边连接两个不同连通块则加入 MST。由于边权递增,最后加入的边必然是当前可选的最大边,即为 MST 的最长边
    • 并查集维护连通性:路径压缩 + 按秩合并保证近乎常数时间的连通性判断
    • MST 性质:Kruskal 的正确性基于"割性质"——全局最小边必在某棵 MST 中,按序选边保证不遗漏更优解
    • 适用于"无向图最小生成树 + 追踪特定边权"类问题,核心是按权排序后用并查集贪心选边

【算法标签】

普及 #生成树

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 2005, M = 10005 * 2;
int n, m, ans, p[N], maxn = -1;  // n: 节点数, m: 边数, maxn: 最大边权
struct Edge  // 存储边
{
    int a, b, w;  // 起点, 终点, 边权
    bool operator < (const Edge &E) const  // 按边权从小到大排序
    {
        return w < E.w;
    }
} edges[M];

int find(int x)  // 并查集查找
{
    if (p[x] != x)
    {
        p[x] = find(p[x]);
    }
    return p[x];
}

void kruskal()  // Kruskal算法
{
    sort(edges + 1, edges + m + 1);  // 边按权值排序
    for (int i = 1; i <= n; i++)  // 初始化并查集
    {
        p[i] = i;
    }
    int res = 0;  // 总权值
    for (int i = 1; i <= m; i++)  // 遍历所有边
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;
        a = find(a), b = find(b);
        if (a != b)  // 不连通,加入生成树
        {
            p[a] = b;  // 合并
            res += w;  // 累加权值
            maxn = max(maxn, w);  // 更新最大边权
        }
    }
}
int main()
{
    cin >> n >> m;  // 输入节点数和边数
    for (int i = 1; i <= m; i++)  // 输入边
    {
        int a, b, w;
        cin >> a >> b >> w;
        edges[i] = {a, b, w};
    }
    kruskal();  // 计算最小生成树
    cout << maxn << endl;  // 输出最大边权
    return 0;
}

【运行结果】

3 3
1 2 23
2 3 1000
1 3 43
43
posted @ 2026-08-27 11:06  团爸讲算法  阅读(9)  评论(0)    收藏  举报