P1265 公路修建 优先队列优化prim

解题思路

问题分析

  1. 题目描述:有n个城市,需要修建公路将它们全部连通。修建规则特殊:

    • 每轮每个城市选择最近的城市申请修路

    • 有特殊规则处理冲突和环路情况

    • 最终目标是计算所有修建公路的总长度

  2. 关键发现:虽然题目描述复杂,但实际可以简化为标准的最小生成树问题。因为:

    • 题目描述的修建过程实际上会构建一棵生成树

    • 特殊规则保证了不会形成环路,且总长度最小

算法选择

  1. Prim算法:适合稠密图的最小生成树算法

    • 本题是完全图(每两个城市间都有潜在的公路)

    • Prim算法时间复杂度O(n²),对于n≤5000可以接受

    • 使用优先队列优化查找最小边的过程

  2. 为什么不用Kruskal

    • Kruskal需要对所有边排序,完全图有O(n²)条边

    • 对于n=5000,边数约2500万,排序和处理的代价太高

实现细节

  1. 距离计算:直接计算每对城市间的欧几里得距离

  2. 优先队列:存储(距离,城市编号)对,快速获取最小距离城市

  3. 标记数组:避免重复处理同一城市

  4. 距离更新:每当新城市加入生成树,更新所有未加入城市到生成树的距离

复杂度分析

  1. 时间复杂度

    • 每个城市处理一次:O(n)

    • 每次处理需要更新n-1个城市的距离:O(n)

    • 优先队列操作:O(log n)

    • 总体:O(n²)

  2. 空间复杂度

    • 存储坐标:O(n)

    • 距离数组和标记数组:O(n)

    • 优先队列:O(n)

正确性证明

  1. Prim算法保证:能够找到连接所有顶点的最小生成树

  2. 题目规则对应

    • "每个城市选择最近城市"对应Prim的贪心选择

    • "避免环路"由生成树性质保证

    • "多城市冲突"由最短距离优先保证

优化空间

  1. 距离计算优化:可以预先计算所有距离,但空间需求大

  2. 更高效堆:使用Fibonacci堆可以优化到O(m + n log n),但实现复杂

  3. 并行计算:对于大规模数据,可以并行处理距离计算

 

 

 

 

 

 

#include<bits/stdc++.h>
#define pii pair<double,int>  // 定义一个pair类型,存储(距离,顶点编号)
using namespace std;
const int N = 1e5 + 10, inf = 0x3f3f3f3f;

vector<pii> g[N];  // 邻接表(虽然本题未使用邻接表存储图)
int n, m;          // n:城市数量,m:未使用
double dis[N];     // 存储各点到当前生成树的最小距离
int vis[N];        // 标记数组,记录顶点是否已加入生成树
int x[N], y[N];    // 存储每个城市的坐标

// 计算两个城市间的欧几里得距离
double dist(int i, int j) {
    return sqrt((x[i]-x[j])*(x[i]-x[j]) + (y[i]-y[j])*(y[i]-y[j]));
}

// Prim算法实现
void prim() {
    // 初始化距离数组
    for(int i = 1; i <= n; i++) dis[i] = 1e12; // 不能用memset初始化double数组
    dis[1] = 0;  // 从城市1开始构建生成树
    
    double sum = 0, ans = 0; // sum:已加入城市数,ans:公路总长度
    priority_queue<pii, vector<pii>, greater<pii>> q; // 最小堆
    q.push({0, 1});  // 初始放入城市1,距离0
    
    while(!q.empty()) {
        int x = q.top().second;  // 取出当前距离最小的城市
        q.pop();
        
        if(vis[x]) continue;  // 如果已访问过则跳过
        vis[x] = 1;           // 标记为已访问
        ans += dis[x];        // 累加这条公路的长度
        sum++;                // 已加入城市数+1
        
        // 更新所有其他城市到生成树的距离
        for(int i = 1; i <= n; i++) {
            if(i == x) continue;  // 跳过自身
            double d = dist(x, i); // 计算距离
            if(dis[i] > d) {      // 如果找到更小的距离
                dis[i] = d;       // 更新距离
                q.push({dis[i], i}); // 加入优先队列
            }
        }
    }
    
    printf("%.2f", ans);  // 输出总长度,保留两位小数
}

int main() {
    cin >> n;
    // 读取每个城市的坐标
    for(int i = 1; i <= n; i++) {
        cin >> x[i] >> y[i];
    }
    prim();  // 执行Prim算法
    return 0;
}

 

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