使用并查集,数据结构设计上除了要设计存储边,还需依据本题额外设计存储结点坐标的结构,读入每个结点的坐标后,还需要预先计算出每条可能的边的权值以供后面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; }