P2323 [HNOI2006] 公路修建问题
解题思路
这道题目是一个典型的最小生成树问题,但有两个特殊要求:
-
必须选择至少k条一级公路
-
需要使所有公路中花费最大的那条的花费尽可能小
方法思路
-
双阶段Kruskal算法:我们分两个阶段来构建生成树
-
第一阶段:只考虑一级公路,构建部分生成树,确保至少有k条一级公路
-
第二阶段:考虑所有公路(优先选择二级公路),完成剩余的连接
-
-
贪心选择:在每个阶段都按照边的权值从小到大排序,确保每次选择的都是当前最小的边
-
并查集管理连通性:使用并查集来高效管理节点的连通性,避免形成环
代码注释
#include<bits/stdc++.h> #define pii pair<int,int> using namespace std; const int N = 2e5 + 10; // 定义边的结构体,包含两个端点、一级/二级公路花费、是否被选中标记、输入时的编号 struct node{ int x,y,z1,z2,f,id; // x,y: 边的两个端点 // z1: 一级公路花费 // z2: 二级公路花费 // f: 是否被选中(0/1) // id: 边的原始输入编号 }; node t[N]; // 存储所有边的数组 int n,m,k; // n:景点数, m:可修公路数, k:需要的一级公路最小数量 int ans,sum; // ans:最大花费, sum:已选边数 pii a[N]; // 存储结果边的数组(first:边编号, second:公路等级) int top; // 结果边的数量 int f[N]; // 并查集父节点数组 // 比较函数:按一级公路花费升序排序 bool cmp1(node a,node b) { return a.z1 < b.z1; } // 比较函数:按二级公路花费升序排序 bool cmp2(node a,node b) { return a.z2 < b.z2; } // 比较函数:按边的原始编号升序排序(用于结果输出) bool cmp3(pii a,pii b) { return a.first < b.first; } // 并查集查找函数 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算法实现 // cnt: 需要选择的边数 // op: 阶段标识(1:第一阶段只选一级公路, 2:第二阶段可选任意公路) void kruskal(int cnt,int op) { for(int i = 1; i <= m - 1; i++) { int x = t[i].x,y = t[i].y; // 如果两个端点不连通且该边未被选中 if(find(x) != find(y) && t[i].f == 0) { merge(x,y); // 合并两个集合 t[i].f = 1; // 标记边为已选 sum++; // 已选边数+1 // 第一阶段处理 if(op == 1) { ans = max(ans,t[i].z1); // 更新最大花费 a[++top] = {t[i].id,op}; // 记录结果(强制一级公路) } // 第二阶段处理 if(op == 2) { // 选择花费较小的公路类型 ans = max(ans,min(t[i].z1,t[i].z2)); // 更新最大花费 if(t[i].z1 <= t[i].z2) a[++top] = {t[i].id,1}; // 选一级公路 else a[++top] = {t[i].id,2}; // 选二级公路 } if(sum == cnt) break; // 已选够所需边数,退出 } } } int main() { cin >> n >> k >> m; // 读入所有边信息 for(int i = 1; i <= m - 1; i++) { int x,y,z1,z2; cin >> x >> y >> z1 >> z2; t[i] = {x,y,z1,z2,0,i}; // 初始化边信息 } // 初始化并查集 for(int i = 1; i <= n; i++) f[i] = i; // 第一阶段:优先选择一级公路,确保至少有k条 sort(t + 1,t + 1 + m - 1,cmp1); // 按一级公路花费排序 kruskal(k,1); // 选择k条一级公路 // 第二阶段:选择剩余需要的边,优先选择二级公路 sort(t + 1,t + 1 + m - 1,cmp2); // 按二级公路花费排序 kruskal(n - 1,2); // 选择剩余n-1-k条边 // 输出结果 cout << ans << endl; // 输出最大花费 sort(a + 1,a + 1 + top,cmp3); // 按原始边编号排序结果 for(int i = 1; i <= top; i++) cout << a[i].first << " " << a[i].second << endl; // 输出每条边的编号和等级 return 0; }
复杂度分析
-
时间复杂度:O(m log m),主要来自两次排序操作
-
空间复杂度:O(n + m),用于存储边和并查集结构
该算法通过两次Kruskal算法,先确保满足一级公路数量要求,再以最小花费完成整个生成树的构建,保证了最优解。

浙公网安备 33010602011771号