题解:洛谷 P2820 局域网

【题目来源】

洛谷:P2820 局域网 - 洛谷

【题目描述】

现在需要解决回路问题,我们将除去一些连线,使得网络中没有回路,不改变原图节点的连通性,并且被除去网线的 \(\sum f(i,j)\) 最大,请求出这个最大值。

【输入】

第一行两个正整数 \(n,k\)

接下来的 \(k\) 行每行三个正整数 \(i,j,m\) 表示 \(i,j\) 两台计算机之间有网线联通,通畅程度为 \(m\)

【输出】

一个正整数, \(\sum f(i,j)\) 的最大值。

【输入样例】

5 5
1 2 8
1 3 1
1 5 3
2 4 5
3 4 2

【输出样例】

8

【核心思想】

  1. 问题分析:给定一个带权无向图,需要删除一些边使得图中无回路且保持连通性(即形成生成树),同时最大化被删除边的权重之和。等价于:在图中找一棵最小生成树,保留生成树上的边,删除其余边,被删除边的权重和 = 总权重 - 最小生成树权重。

  2. 算法选择

    • Kruskal 算法:按边权从小到大排序,用并查集维护连通性,每次选择权值最小且不形成环的边
    • 补集转化:直接求"删除边权和最大"等价于求"保留边权和最小",即最小生成树
  3. 关键步骤

    • 读入数据\(n, m\)\(m\) 条边 \((a, b, c)\),累加总权重 sum += c
    • Kruskal 算法
      • 边按权重升序排序
      • 初始化并查集 p[i] = i
      • 遍历每条边,若两端点不在同一集合,则合并并累加权重 res
      • 若最终 cnt < n - 1,图不连通,返回无穷大
    • 计算答案sum - res
  4. 时间/空间复杂度

    • 时间复杂度:\(O(m \log m)\),排序 \(O(m \log m)\),并查集操作近似 \(O(1)\)
    • 空间复杂度:\(O(n + m)\),并查集和边数组
  5. 最小生成树补集思想的核心思想

    • 问题等价转化:"删除边权和最大" = "总权重 - 保留边权和最小" = "总权重 - 最小生成树权重"
    • 贪心最优性:Kruskal 算法每次选择权值最小的不形成环的边,保证保留边的总权重最小,从而删除边的总权重最大
    • 并查集判环:两个端点已在同一集合说明加入该边会形成环,直接跳过
    • 连通性检查:生成树需要恰好 \(n-1\) 条边,若不足则原图不连通,无法形成生成树
    • 适用于"在保持连通的前提下去除最大权重"的问题,核心在于转化为最小生成树问题

【算法标签】

普及 #生成树

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 105, M = 1005, INF = 1e9;
int n, m, sum;  // n: 节点数量,m: 边数量,sum: 所有边的权重总和
int p[N];  // 并查集父节点数组

// 边结构体
struct Edge
{
    int a, b, w;  // a: 边的一个端点,b: 边的另一个端点,w: 边的权重
    bool operator < (const Edge &E)const
    {
        return w < E.w;  // 按照权重从小到大排序
    }
}edges[M];

// 并查集查找操作,查找x的祖先节点(路径压缩)
int find(int x)
{
    if (p[x] != x)  // 如果x不是根节点
        p[x] = find(p[x]);  // 递归查找并路径压缩
    return p[x];
}

// Kruskal算法求最小生成树
int kruskal()
{
    sort(edges + 1, edges + m + 1);  // 按照边的权重从小到大排序
    for (int i = 1; i <= n; i++)  // 初始化并查集,每个节点自成一个集合
        p[i] = i;

    int res = 0, cnt = 0;  // res: 最小生成树的权重和,cnt: 已选边数
    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);  // 查找a和b所在的集合
        if (a != b)  // 如果a和b不在同一集合中(不会形成环)
        {
            p[a] = b;  // 合并两个集合
            res += w;  // 累加边权重
            cnt++;  // 已选边数加1
        }
    }

    if (cnt < n - 1)  // 如果选择的边数小于n-1,说明图不连通
        return INF;
    return res;  // 返回最小生成树的权重和
}

int main()
{
    cin >> n >> m;  // 输入节点数量和边数量
    for (int i = 1; i <= m; i++)  // 输入每条边的信息
    {
        int a, b, c;
        cin >> a >> b >> c;
        if (a > b)  // 确保a <= b,便于处理
            swap(a, b);
        edges[i] = {a, b, c};  // 存储边
        sum += c;  // 累加所有边的权重
    }

    int ans = kruskal();  // 计算最小生成树的权重和
    cout << sum - ans << endl;  // 输出最大生成森林的权重和
    return 0;
}

【运行结果】

5 5
1 2 8
1 3 1
1 5 3
2 4 5
3 4 2
8
posted @ 2026-08-10 12:28  团爸讲算法  阅读(14)  评论(0)    收藏  举报