P2330 [SCOI2005] 繁忙的都市

解题思路

这道题目是一个典型的最小生成树(Minimum Spanning Tree, MST)问题,需要满足以下要求:

  1. 选择的边能够连通所有节点(形成生成树)

  2. 选择的边尽可能少(对于生成树来说,边数固定为n-1)

  3. 选择的边中最大权值尽可能小

方法思路

  1. Kruskal算法:使用贪心策略,按边的权值从小到大排序,依次选择不会形成环的边,直到形成生成树

  2. 并查集管理连通性:高效判断边的两个端点是否已经连通,避免形成环

  3. 记录最大边权:在构建生成树的过程中,记录选中的边的最大权值

代码注释

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;

// 定义边的结构体,包含两个端点和权值
struct node{
    int x,y,z;  // x,y:边的两个端点,z:边的权值(分值)
};
node a[N];     // 存储所有边的数组
int n,m;       // n:交叉路口数量,m:道路数量
int f[N];      // 并查集父节点数组

// 比较函数:按边的权值升序排序
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)
{
    f[find(x)] = find(y);  // 将x的根节点指向y的根节点
}

// Kruskal算法实现
void kruskal()
{
    // 初始化并查集,每个节点是自己的父节点
    for(int i = 1; i <= n; i++) f[i] = i;
    
    int sum = 0, ans = 0;  // sum:已选边数,ans:当前最大边权
    
    // 遍历所有边(已按权值排序)
    for(int i = 1; i <= m; i++)
    {
        // 如果边的两个端点不连通
        if(find(a[i].x) != find(a[i].y))
        {
            merge(a[i].x,a[i].y);  // 合并两个集合
            sum++;                // 已选边数+1
            ans = max(ans,a[i].z); // 更新最大边权
            
            // 已形成生成树(n-1条边),提前退出
            if(sum == n - 1)break;
        }
    }
    
    // 输出结果:边数和最大边权
    cout << sum << " " << ans << endl;
}

int main()
{
    // 读入数据
    cin >> n >> m;
    for(int i = 1; i <= m; i++) 
        cin >> a[i].x >> a[i].y >> a[i].z;
    
    // 按边权升序排序
    sort(a + 1,a + 1 + m, cmp);
    
    // 执行Kruskal算法
    kruskal();
    
    return 0;
}

复杂度分析

  1. 时间复杂度:

    • 排序操作:O(m log m)

    • Kruskal算法:O(m α(n)),其中α(n)是反阿克曼函数,可以认为是常数

    • 总体复杂度:O(m log m)(排序主导)

  2. 空间复杂度:O(n + m),用于存储边和并查集结构

该算法通过Kruskal算法构建最小生成树,保证了边数最少(n-1条),同时最大边权最小,完全符合题目要求。

posted @ 2025-05-27 20:23  CRt0729  阅读(18)  评论(0)    收藏  举报