You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

AcWing 346 走廊泼水节

一、题目描述

话说,中中带领的Oier们打算举行一次冬季泼水节,当然这是要秘密进行的,绝对不可以让中中知道。不过中中可是老江湖了,当然很快就发现了我们的小阴谋,于是他准备好水枪迫不及待的想要加入我们了。 我们一共有NOier打算参加这个泼水节,同时很凑巧的是正好有N个水龙头(至于为什么,我不解释)。N个水龙头之间正好有N-1条小道,并且每个水龙头都可以经过小道到达其他水龙头(这是一棵树,你应该懂的..)。但是Oier们为了迎接中中的挑战,决定修建一些个道路(至于怎么修,秘密 ~ ),使得每个水龙头到每个水龙头之间都有一条直接的道路连接 ( 也就是构成一个完全图 呗 ~ )。但是Oier们很懒得,并且记性也不好,他们只会去走那N-1条小道,并且希望所有水龙头之间修建的道路,都要 大于 两个水龙头之前连接的所有小道( 小道当然要是最短 的了)。所以神COW们,帮那些Oier们计算一下吧,修建的那些道路总长度 最短 是多少,毕竟修建道路是要破费的~~

二、题目大意

给定一棵 N 个节点的树,要求 增加若干条边,把这棵树扩充为 完全图,并满足图的 唯一 最小生成树 仍然是这棵树。

求增加的边的权值总和最小是多少

注意 树中的所有边权均为整数,且新加的所有边权也必须为整数。

输入格式 第一行包含整数 t,表示共有 t 组测试数据。

对于每组测试数据,第一行包含整数 N

接下来 N1 行,每行三个整数 X,Y,Z,表示 X 节点与 Y 节点之间存在一条边,长度为 Z

输出格式 每组数据输出一个整数,表示权值总和最小值。

每个结果占一行。

数据范围 1≤N≤6000

1≤Z≤100

输入样例

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

输出样例

4
17 

三、题目解析

解释(1,4),(1,3),(2,4)共三条边需要连接上,才能构成完全图,(2,3)是不需要连接的,因为它是最小生成树的一部分。

Kruskal算法,在每循环到一条可以合并两个连通块的边edge时,记edge的边长为c,为了形成一个完全图,就要使得两个已经是完全图的连通块中的点有边,但是为了使最后的唯一最小生成树还是原来那棵而且,新增的边一定要大于c

  • 假设新边小于c,因为新增边后会成环,当断开边edge形成的树大小会变小,即不是原来那棵,所以不成立

  • 假设新边等于c,同样的断开edge,会形成一个大小一样但结构不一样的树,不满足唯一,所以也不成立

所以只要在每次新增edge的时候,给两个连通块内的点增加 c+1 长的边即可。

Code

#include <bits/stdc++.h>
using namespace std;

const int N = 6010;

struct Edge {
    int a, b, c;
    const bool operator<(const Edge &t) const {
        return c < t.c;
    }
} edge[N];
int n;

int cnt[N]; // 配合并查集使用的,记录家族人员数量
int p[N];   // 并查集
int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> n;
        for (int i = 1; i <= n; i++) p[i] = i, cnt[i] = 1; // 并查集初始化

        // 录入n-1条边
        int el = n - 1;
        for (int i = 0; i < el; i++) {
            int a, b, c;
            cin >> a >> b >> c;
            edge[i] = {a, b, c};
        }
        // 排序
        sort(edge, edge + el);

        int res = 0;
        for (int i = 0; i < el; i++) {
            int a = find(edge[i].a), b = find(edge[i].b), c = edge[i].c;
            if (a != b) {
                // a集合数量b集合数量相乘但需要减去已经建立的最小生成权这条边
                // c是最小的其它的可以建立最小也得大于c,即c+1
                res += (cnt[a] * cnt[b] - 1) * (c + 1);
                p[a] = b;         // 合并到同一集合
                cnt[b] += cnt[a]; // b家族人数增加cnt[a]个,并查集数量合并
            }
        }
        // 输出
        cout << res << endl;
    }
    return 0;
}