使用并查集,数据结构设计上除了要设计存储边,还需依据本题额外设计存储结点坐标的结构,读入每个结点的坐标后,还需要预先计算出每条可能的边的权值以供后面Kruskal选择边;计算每条边时不需要保留两位小数,如此会有精度上的缺失,计算出最终结果后再保留两位小数

#include <cstdio>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;

#define N 101
int father[N], height[N];

struct node {
    double x, y; // 结点的横纵坐标
} Node[N]; // 结点的结构体数组,从0开始编号

struct Edge {
    double s, t; // 边的起始端点和结束端点
    double weight;
};

bool cmp(Edge lhs, Edge rhs) {
    return lhs.weight < rhs.weight;
}

void Init(int n) {
    for (int i = 0; i < n; i++) {
        father[i] = i;
        height[i] = 1;
    }
}

int Find(int x) {
    if (x != father[x]) {
        father[x] = Find(father[x]);
    }
    return father[x];
}

void Union(double x, double y, double weight, double &ans) {
    int m = Find(x);
    int n = Find(y);
    if (m != n) {
        ans += weight;
    }
    if (height[m] > height[n]) {
        father[n] = m;
    }
    else if (height[m] < height[n]) {
        father[m] = n;
    }
    else {
        father[n] = m; // 将n并入m,以m为根的树高度+1
        ++height[m];
    }
}

// 最小生成树,Kruskal,并查集
int main() {
    int n; // 坐标的数量
    scanf("%d", &n);
    Init(n); // 初始化并查集
    double ans = 0, weight;
    vector<Edge> graph;
    // 读入结点的横纵坐标
    for (int i = 0; i < n; i++) {
        scanf("%lf%lf", &Node[i].x, &Node[i].y);
    }
    // 计算n个结点n*(n-1)/2条边的权重,两个结点i和j一条边
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            Edge edge;
            edge.s = i;
            edge.t = j;
            weight = sqrt(pow(abs(Node[i].x - Node[j].x), 2) + pow(abs(Node[i].y - Node[j].y), 2));
            edge.weight = weight;
            graph.push_back(edge);
        }
    }
    sort(graph.begin(), graph.end(), cmp); // 按边的权值排序
    for (int i = 0; i < n * (n - 1) / 2; i++) { // 按从大到小的顺序遍历每条边,选择合适的边加入最小生成树
        Union(graph[i].s, graph[i].t, graph[i].weight, ans);
    }
    printf("%.2f\n", ans);
    return 0;
}