一、题目描述
现需要在某城市计划建设 5G 网络,选取了 N 个地点部署基站(编号 1~N,0 < N ≤ 20)。需通过光纤连接基站实现互联互通,不同基站间架设光纤的成本不同,且部分基站间已存在光纤连接。要求设计算法计算联通所有基站的最小建设成本:
**注意:**基站的联通具有传递性,比如基站A与基站B架设了光纤,基站B与基站C也架设了光纤,则基站A与基站C视为可以互相联通。
二、输入输出描述
输入描述
- 第一行:基站总数
N(0 < N ≤ 20); - 第二行:具备光纤直连条件的基站对数目
M(0 < M < N*(N-1)/2); - 后续
M行:每行格式为X Y Z P,其中:X,Y:基站编号(0 < X,Y ≤ N,X≠Y);Z:X、Y 间架设光纤的成本(0 < Z < 100);P:是否已存在光纤连接(0 = 未连接,1 = 已连接)。
输出描述
- 如果给定条件,可以建设成功互联互通的5G网络,则输出最小的建设成本;
- 如果给定条件,无法建设成功互联互通的5G网络,则输出 -1。
三、示例
|----|--------------------------------|
| 输入 | 3 3 1 2 3 0 1 3 1 0 2 3 5 0 |
| 输出 | 4 |
| 说明 | 只需要在1,2以及1,3基站之间铺设光纤,其成本为3+1=4 |
|----|-------------------|
| 输入 | 3 1 1 2 5 0 |
| 输出 | -1 |
| 说明 | 3基站无法与其他基站连接,输出-1 |
|----|----------------------------------|
| 输入 | 3 3 1 2 3 0 1 3 1 0 2 3 5 1 |
| 输出 | 1 |
| 说明 | 2,3基站已有光纤相连,只要在1,3基站之间铺设光纤,其成本为1 |
四、解题思路
1. 核心思想
核心是克鲁斯卡尔(Kruskal)算法 + 并查集(Union-Find):
- 问题本质是带权无向图的最小生成树(MST) 问题:基站为节点,光纤为边,成本为边权,已连接的边权为 0;
- 并查集用于高效管理节点的联通性,判断新增边是否会形成环;
- 按边权升序遍历所有边,依次将边加入生成树(无环则合并节点),直到所有节点联通;
- 累加新增边的权值(已连接的边权为 0,无成本),最终若形成最小生成树则输出总成本,否则输出
-1。
2. 问题本质分析
- 问题本质是最小生成树求解 :
- 已存在的光纤(P=1)可视为 "零成本" 边,优先加入生成树;
- 未连接的光纤(P=0)按成本升序排序,尽可能选择低成本边连接分散的联通分量;
- 并查集是解决最小生成树问题的高效工具,可在 O (α(N))(近似常数)时间内完成节点合并与联通性查询;
- 核心约束:N≤20,算法时间复杂度无性能压力,优先保证逻辑清晰。
3. 核心逻辑
- 边预处理 :
- 将所有基站对(边)转换为统一格式,已连接的边(P=1)权值设为 0,未连接的边权值为 Z;
- 按边权升序排序(优先选零成本、低成本边);
- 并查集初始化:每个基站初始为独立的联通分量(父节点为自身);
- Kruskal 算法执行 :
- 遍历排序后的边,对每条边的两个节点,若不在同一联通分量则合并,并累加边权到总成本;
- 若所有节点已联通,提前终止遍历;
- 联通性校验 :遍历结束后,检查所有节点是否属于同一联通分量,是则输出总成本,否则输出
-1。
4. 步骤拆解
- 输入解析:读取 N、M,以及 M 条边的信息,转换为边对象(含起点、终点、权值);
- 边预处理 :
- 对每条边,若 P=1 则权值设为 0,否则为 Z;
- 按权值升序排序所有边;
- 并查集初始化:创建父节点数组,parent [i] = i(i 为基站编号);
- 最小生成树构建 :
- 初始化总成本为 0,已选边数为 0;
- 遍历排序后的边:
- 查找两个节点的根节点,若不同则合并,累加边权到总成本,已选边数 + 1;
- 若已选边数 == N-1(生成树边数 = 节点数 - 1),提前退出;
- 结果校验 :
- 检查所有节点是否联通(根节点相同);
- 联通则输出总成本,否则输出
-1。
五、代码实现
java
import java.util.*;
public class Min5GNetworkCost {
// 并查集父节点数组(基站编号1~N,数组长度N+1)
private static int[] parent;
// 边对象:存储起点、终点、权值
static class Edge implements Comparable<Edge> {
int from;
int to;
int weight;
public Edge(int from, int to, int weight) {
this.from = from;
this.to = to;
this.weight = weight;
}
// 按权值升序排序
@Override
public int compareTo(Edge o) {
return Integer.compare(this.weight, o.weight);
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 1. 输入解析
int N = scanner.nextInt(); // 基站总数
int M = scanner.nextInt(); // 边数
scanner.nextLine(); // 吸收换行
List<Edge> edges = new ArrayList<>();
for (int i = 0; i < M; i++) {
int X = scanner.nextInt();
int Y = scanner.nextInt();
int Z = scanner.nextInt();
int P = scanner.nextInt();
// 预处理边权:已连接(P=1)则权值为0,否则为Z
int weight = P == 1 ? 0 : Z;
edges.add(new Edge(X, Y, weight));
}
scanner.close();
// 2. 初始化并查集
parent = new int[N + 1];
for (int i = 1; i <= N; i++) {
parent[i] = i;
}
// 3. 按边权升序排序
Collections.sort(edges);
// 4. 执行Kruskal算法
int totalCost = 0;
int selectedEdges = 0; // 已选边数(生成树需N-1条边)
for (Edge edge : edges) {
int rootFrom = find(edge.from);
int rootTo = find(edge.to);
// 两个节点不在同一联通分量,合并
if (rootFrom != rootTo) {
union(rootFrom, rootTo);
totalCost += edge.weight;
selectedEdges++;
// 生成树已完成(N-1条边),提前退出
if (selectedEdges == N - 1) {
break;
}
}
}
// 5. 校验是否所有基站联通
boolean isConnected = true;
int root = find(1); // 以第一个基站为基准
for (int i = 2; i <= N; i++) {
if (find(i) != root) {
isConnected = false;
break;
}
}
// 6. 输出结果
if (isConnected) {
System.out.println(totalCost);
} else {
System.out.println(-1);
}
}
// 并查集:查找根节点(路径压缩)
private static int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]); // 路径压缩,优化后续查询
}
return parent[x];
}
// 并查集:合并两个节点(按根节点合并)
private static void union(int x, int y) {
parent[y] = x;
}
}