P2330 [SCOI2005] 繁忙的都市
解题思路
这道题目是一个典型的最小生成树(Minimum Spanning Tree, MST)问题,需要满足以下要求:
-
选择的边能够连通所有节点(形成生成树)
-
选择的边尽可能少(对于生成树来说,边数固定为n-1)
-
选择的边中最大权值尽可能小
方法思路
-
Kruskal算法:使用贪心策略,按边的权值从小到大排序,依次选择不会形成环的边,直到形成生成树
-
并查集管理连通性:高效判断边的两个端点是否已经连通,避免形成环
-
记录最大边权:在构建生成树的过程中,记录选中的边的最大权值
代码注释
#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; }
复杂度分析
-
时间复杂度:
-
排序操作:O(m log m)
-
Kruskal算法:O(m α(n)),其中α(n)是反阿克曼函数,可以认为是常数
-
总体复杂度:O(m log m)(排序主导)
-
-
空间复杂度:O(n + m),用于存储边和并查集结构
该算法通过Kruskal算法构建最小生成树,保证了边数最少(n-1条),同时最大边权最小,完全符合题目要求。

浙公网安备 33010602011771号