[SMOJ1426]最小生成树

97 篇文章 0 订阅
10 篇文章 0 订阅

题目描述

某个宇宙帝国有 n 个星球,由于宇宙的空间是三维的,因此每个星球的位置可以用三维坐标来表示 (x,y,z)。 任意两个不同的星球 i j 都有一条边相连,边的距离是这样计算的: disij=min(|xixj|,|yiyj|,|zizj|) 。 其中| |符号表示取绝对值。现在让你来挑 n1 条边,让这 n 个星球连通成一个最小生成树,输出构成最小生成树的 n1 条边的长度总和。

输入格式 1426.in

第一行,一个整数 n 。 1n100000
接下来有 n 行,每行三个整数: x, y , z。表示一个星球的坐标, 1000000000x,y,z1000000000 。 没有两个星球的位置完全重叠。

输出格式 1426.out

一行,构成最小生成树的 n1 条边的长度总和。

输入样例 1426.in

5
11  -15  -15
14  -5  -15
-1  -1  -5
10  -4  -1
19  -4  19

输出样例 1426.out

4


首先回忆一下最小生成树问题的 Kruskal 算法:按耗费递增的顺序来考虑每条边,每次考虑一条边。当考虑某条边时,若将其加入到已选边的集合中会出现环路,则将其抛弃,否则,将它选入。

在本题中,如果直接枚举每两个星球之间的距离,再做一遍 Kruskal ,很显然是会 TLE 的。我们可以这样想:对于两条边 i j ,它们之间的距离是它们坐标的三个值的差当中最小的。不妨猜想,如果边 (i,j) 出现在最终的最小生成树中,它们必定的 x y z 在分别按 x y z 排序后的序列中相邻。(有点拗口,但是应该没毛病)

容易作个简单的推论:以 x 值为例,如果各星球按 x 排序之后,选中的不是两个相邻的星球,而是两个不相邻的星球,那么一定可以把这条边去掉,用更优的方案替换。

又或者,直接回到从贪心算法的角度来说,我们的目标是让所有星球连通,且总费用最小。那么只要考虑如何选取边才能使费用最小。显然,最终目的是要让全部边连通,那么我们与其选择用差值更大的情况,不如用按某个值排序后相邻的边,这样的选取原则不会存在更优的方案。

综上所述,只需要分别按各星球的 x y z 值排序,每次将相邻的星球连边,这样一共有(星球数-1)*3条边。对这些边做 Kruskal 即可。

时间主要花费在排序上,总的时间复杂度为 O(nlog2n)

参考代码:

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>

using namespace std;

const int maxn = 1e5 + 100;

struct Tplanet {
    int x, y, z;
    int id;
} planet[maxn];

struct Edge {
    int u, v, w;
    Edge () : u(0), v(0), w(0) {}
    Edge (int x, int y, int z) : u(x), v(y), w(z) {}
    bool operator < (const Edge x) const { return w < x.w; }
} edge[maxn * 3];

int n;
int fa[maxn];

bool byX(Tplanet i, Tplanet j) { return i.x < j.x; }
bool byY(Tplanet i, Tplanet j) { return i.y < j.y; }
bool byZ(Tplanet i, Tplanet j) { return i.z < j.z; }

int find(int root) { return fa[root] == root ? root : fa[root] = find(fa[root]); }

int main(void) {
    freopen("1426.in", "r", stdin);
    freopen("1426.out", "w", stdout);
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d%d%d", &planet[i].x, &planet[i].y, &planet[i].z);
        planet[i].id = i;
        fa[i] = i;
    }
    int cnt = 0;
    sort(planet, planet + n, byX);
    for (int i = 1; i < n; i++) edge[cnt++] = Edge(planet[i - 1].id, planet[i].id, planet[i].x - planet[i - 1].x);
    sort(planet, planet + n, byY);
    for (int i = 1; i < n; i++) edge[cnt++] = Edge(planet[i - 1].id, planet[i].id, planet[i].y - planet[i - 1].y);
    sort(planet, planet + n, byZ);
    for (int i = 1; i < n; i++) edge[cnt++] = Edge(planet[i - 1].id, planet[i].id, planet[i].z - planet[i - 1].z);
    sort(edge, edge + cnt);
    long long ans = 0;
    for (int i = 0; i < cnt; i++) {
        int fa_u = find(edge[i].u), fa_v = find(edge[i].v);
        if (fa_u != fa_v) { fa[fa_u] = fa_v; ans += edge[i].w; }
    }
    printf("%lld\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值