P2872 [USACO07DEC] Building Roads S 最小生成树

解题思路

问题分析

  1. 问题描述:给定平面上的n个点,其中部分点之间已有道路连接,需要添加最少的道路使所有点连通,且添加的道路总长度最小。

  2. 关键点:这是一个典型的最小生成树(MST)问题,需要考虑已有的道路。

算法选择

  1. Kruskal算法:适合稀疏图,通过并查集高效管理连通分量,按边权排序后逐步选择最小边。

  2. 优势:能直接利用已有道路,只需处理未连接的顶点。

实现步骤

  1. 预处理

    • 计算所有点对间的距离(完全图边)

    • 将已有道路加入并查集,减少后续处理量

  2. Kruskal核心

    • 按边权升序排序

    • 依次选择不会形成环的最小边(即连接不同连通分量的边)

    • 使用并查集高效判断和合并连通分量

  3. 终止条件:当选中n-1条边时,所有顶点已连通

复杂度分析

  1. 时间复杂度

    • 计算所有边:O(n²)

    • 排序:O(n² log n)(主要瓶颈)

    • Kruskal算法:O(n² α(n))(α为反阿克曼函数)

  2. 空间复杂度:O(n²)存储所有边

优化点

  1. 精度处理:使用double类型存储距离,确保计算精度

  2. 提前终止:当已形成生成树时立即退出循环

  3. 已有道路处理:直接合并相关连通分量,减少后续处理量

注意事项

  1. 输入规模:n≤1000,完全图边数约50万条,在题目限制内

  2. 输出格式:保留两位小数,避免四舍五入误差

  3. 重复道路:题目保证输入合法,无需特殊处理

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10, inf = 0x3f3f3f3f;

// 定义边的结构体:x和y是顶点,z是边的长度
struct node {
    int x, y;
    double z;
};

node t[N];  // 存储所有可能的边
int x[N], y[N];  // 存储每个点的坐标
int f[N];  // 并查集数组
int n, m, cnt, sum;  // n:点数,m:已有边数,cnt:总边数,sum:已连接边数

// 计算两点间欧几里得距离
double dis(int i, int j) {
    return (double)sqrt((double)(x[i] - x[j]) * (x[i] - x[j]) * 1.0 
           + (double)(y[i] - y[j]) * (y[i] - y[j]));
}

// 边按长度从小到大排序的比较函数
bool cmp(node a, node b) {
    return a.z < b.z;
}

// 并查集查找函数(带路径压缩)
int find(int x) {
    if(f[x] != x) f[x] = find(f[x]);
    return f[x];
}

// 并查集合并函数
void merge(int x, int y) {
    int fx = find(x), fy = find(y);
    f[fy] = fx;
}

// Kruskal算法实现
void kruskal() {
    double ans = 0;  // 存储需要修建的道路总长度
    
    // 遍历所有边(已按长度排序)
    for(int i = 1; i <= cnt; i++) {
        int u = t[i].x, v = t[i].y;
        
        // 如果两个顶点不在同一连通分量
        if(find(u) != find(v)) {
            merge(u, v);  // 合并两个连通分量
            sum++;  // 已连接边数+1
            ans += t[i].z;  // 累加道路长度
            
            // 如果已经形成生成树(n-1条边),提前退出
            if(sum == n - 1) break;
        }
    }
    
    // 输出结果,保留两位小数
    printf("%.2f", ans);
}

int main() {
    cin >> n >> m;
    
    // 读取每个点的坐标,并初始化并查集
    for(int i = 1; i <= n; i++) {
        cin >> x[i] >> y[i];
        f[i] = i;  // 每个点初始时自成一个连通分量
    }
    
    // 生成所有可能的边(完全图)
    for(int i = 1; i <= n; i++) {
        for(int j = i + 1; j <= n; j++) {
            if(i == j) continue;  // 跳过自环
            t[++cnt] = {i, j, dis(i, j)};  // 存储边信息
        }
    }
    
    // 处理已经存在的道路
    for(int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        if(find(u) != find(v)) {
            merge(u, v);  // 合并连通分量
            sum++;  // 已连接边数+1
        }
    }
    
    // 按边长度排序
    sort(t + 1, t + 1 + cnt, cmp);
    
    // 执行Kruskal算法
    kruskal();
    
    return 0;
}

 

posted @ 2025-04-28 20:39  CRt0729  阅读(21)  评论(0)    收藏  举报