题目描述

局域网内有 nn 台计算机、kk 条网线,第 ee 条连接 ie,jei_e, j_e,通畅程度为 mem_e(n⩽100n \leqslant 100,me⩽1000m_e \leqslant 1000,越小越通畅)。要删去一些网线使剩下的网络没有回路,且被删网线的通畅程度之和最大,求这个最大值。

输入第一行两个正整数 n kn\ k,之后 kk 行每行三个正整数 i j mi\ j\ m;输出一个正整数,即被删网线通畅程度之和的最大值。

输入样例 输出样例
5 5 / 1 2 8 / 1 3 1 / 1 5 3 / 2 4 5 / 3 4 2 8

思路

删去的权值和等于总权值和减去保留的权值和,所以要让删去的最大,只需让保留的最小;而保留的网线无回路且保持连通,恰好是一棵生成树。于是问题化为求最小生成树:把边按权值升序排序,用并查集判断两端点是否已连通,未连通就保留并合并,否则会成环、必须删掉。答案即总权值和减去最小生成树权值和。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-30 08:20
# update_at: 2026-09-30 08:24

import sys
from collections.abc import Iterable


def mst_weight(n: int, edges: list[tuple[int, int, int]]) -> int:
    """返回最小生成树的边权和;图不连通时只算能连通部分的最小生成森林。"""
    parent = list(range(n + 1))  # 并查集:parent[x] == x 表示 x 还是一个集合的根

    def find(x: int) -> int:
        """返回 x 所在集合的根,顺手做路径压缩。"""
        while parent[x] != x:
            parent[x] = parent[parent[x]]  # 隔代压缩,写起来比递归浅
            x = parent[x]
        return x

    total = 0
    used = 0  # 已并入生成树的边数,等于 n-1 时整张图已经连通
    for w, u, v in sorted(edges):  # 元组排序先比 w,权重升序就是 Kruskal 的贪心顺序
        ru, rv = find(u), find(v)
        if ru == rv:
            continue  # 两端已经连通,这条边加上去正好成环,必须删掉
        parent[ru] = rv
        total += w
        used += 1
        if used == n - 1:
            break
    return total


def solve() -> None:
    data: Iterable[int] = map(int, sys.stdin.buffer.read().split())
    it = iter(data)
    n, k = next(it), next(it)
    edges = []
    total = 0
    for _ in range(k):
        i, j, m = next(it), next(it), next(it)
        edges.append((m, i, j))  # 权重放到第 0 位,Kruskal 直接按元组排序即可
        total += m
    keep = mst_weight(n, edges)
    print(total - keep)  # 总量固定,留下的越少被删掉的越多


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-05 12:30
 * update_at: 2026-10-05 12:30
 */

#include <cstdio>
#include <algorithm>

using namespace std;

typedef long long ll;

const int MAXK = 10005; // n<=100,最多 n*(n-1)/2 条边,开大一倍留余量

struct Edge {
    ll w; // 通畅程度,越小越通畅
    ll u;
    ll v;
};

Edge edge[MAXK]; // 保存所有网线,Kruskal 按 w 升序扫描

ll parent[105]; // 并查集:parent[x] 表示 x 所在集合的父节点

// 返回 x 所在集合的根,路径压缩
ll find_root(ll x) {
    while (parent[x] != x) {
        parent[x] = parent[parent[x]];
        x = parent[x];
    }
    return x;
}

// Kruskal 的排序标准:按通畅程度升序
bool cmp_edge(Edge a, Edge b) {
    return a.w < b.w;
}

int main() {
    ll n, k;
    scanf("%lld %lld", &n, &k);

    ll total = 0; // 所有网线通畅程度之和,是常数部分
    for (ll e = 1; e <= k; e++) {
        scanf("%lld %lld %lld", &edge[e].u, &edge[e].v, &edge[e].w);
        total = total + edge[e].w;
    }
    sort(edge + 1, edge + k + 1, cmp_edge);

    for (ll i = 1; i <= n; i++) {
        parent[i] = i; // 初始时每台计算机自成一个集合
    }

    ll keep = 0;  // 最小生成树的权值和
    ll used = 0;  // 已并入生成树的边数,达到 n-1 时全图已连通
    for (ll e = 1; e <= k; e++) {
        ll ru = find_root(edge[e].u);
        ll rv = find_root(edge[e].v);
        if (ru == rv) {
            continue; // 两端已连通,留下这条边会成环,必须删掉
        }
        parent[ru] = rv;
        keep = keep + edge[e].w;
        used++;
        if (used == n - 1) {
            break;
        }
    }

    // 删边权和最大 = 总权值和 - 保留的最小权值和
    printf("%lld\n", total - keep);
    return 0;
}