【华为OD】5G网络建设
题目描述
现需要在某城市进行 5G 网络建设,已经选取 N 个地点设置 5G基站,编号固定为 1 到 N,接下来需要各个基站之间使用光纤进行连接以确保基站能互联互通,不同基站之间架设光纤的成本各不相同,且有些节点之间已经存在光纤相连,请你设计算法,计算出能联通这些基站的最小成本是多少。
注意,基站的联通具有传递性,即基站 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 <= N,0 < 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 以及 2,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 站点 2 向铺设,其成本为 1
解题思路
这是一个典型的 最小生成树(MST) 问题。我们需要找到连接所有基站的最小成本。
关键点:
- 已经存在的光纤连接(P=1)成本为0,需要建设的光纤连接(P=0)成本为Z
- 需要判断是否能够连通所有基站
- 如果能连通,求最小生成树的权重和
我将提供两种经典的最小生成树算法:Kruskal算法和Prim算法。
解法一:Kruskal算法(基于边的贪心算法)
Kruskal算法的核心思想是:
- 将所有边按权重排序
- 使用并查集维护连通性
- 依次选择权重最小且不会形成环的边
Java实现
import java.util.*;
public class Solution {
static class Edge implements Comparable<Edge> {
int u, v, cost;
public Edge(int u, int v, int cost) {
this.u = u;
this.v = v;
this.cost = cost;
}
@Override
public int compareTo(Edge other) {
return Integer.compare(this.cost, other.cost);
}
}
static class UnionFind {
int[] parent, rank;
int components;
public UnionFind(int n) {
parent = new int[n + 1];
rank = new int[n + 1];
components = n;
for (int i = 1; i <= n; i++) {
parent[i] = i;
}
}
public int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
public boolean union(int x, int y) {
int px = find(x), py = find(y);
if (px == py) return false;
if (rank[px] < rank[py]) {
parent[px] = py;
} else if (rank[px] > rank[py]) {
parent[py] = px;
} else {
parent[py] = px;
rank[px]++;
}
components--;
return true;
}
public boolean isConnected() {
return components == 1;
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
List<Edge> edges = new ArrayList<>();
UnionFind uf = new UnionFind(n);
for (int i = 0; i < m; i++) {
int x = sc.nextInt();
int y = sc.nextInt();
int z = sc.nextInt();
int p = sc.nextInt();
int cost = (p == 1) ? 0 : z;
edges.add(new Edge(x, y, cost));
// 如果已经连接,先在并查集中合并
if (p == 1) {
uf.union(x, y);
}
}
Collections.sort(edges);
int totalCost = 0;
for (Edge edge : edges) {
if (uf.union(edge.u, edge.v)) {
totalCost += edge.cost;
}
}
if (uf.isConnected()) {
System.out.println(totalCost);
} else {
System.out.println(-1);
}
}
}
Python实现
class UnionFind:
def __init__(self, n):
self.parent = list(range(n + 1))
self.rank = [0] * (n + 1)
self.components = n
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
px, py = self.find(x), self.find(y)
if px == py:
return False
if self.rank[px] < self.rank[py]:
self.parent[px] = py
elif self.rank[px] > self.rank[py]:
self.parent[py] = px
else:
self.parent[py] = px
self.rank[px] += 1
self.components -= 1
return True
def is_connected(self):
return self.components == 1
def solve():
n = int(input())
m = int(input())
edges = []
uf = UnionFind(n)
for _ in range(m):
x, y, z, p = map(int, input().split())
cost = 0 if p == 1 else z
edges.append((cost, x, y))
# 如果已经连接,先在并查集中合并
if p == 1:
uf.union(x, y)
edges.sort()
total_cost = 0
for cost, u, v in edges:
if uf.union(u, v):
total_cost += cost
if uf.is_connected():
print(total_cost)
else:
print(-1)
solve()
C++实现
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Edge {
int u, v, cost;
bool operator<(const Edge& other) const {
return cost < other.cost;
}
};
class UnionFind {
public:
vector<int> parent, rank;
int components;
UnionFind(int n) : parent(n + 1), rank(n + 1, 0), components(n) {
for (int i = 1; i <= n; i++) {
parent[i] = i;
}
}
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
bool unite(int x, int y) {
int px = find(x), py = find(y);
if (px == py) return false;
if (rank[px] < rank[py]) {
parent[px] = py;
} else if (rank[px] > rank[py]) {
parent[py] = px;
} else {
parent[py] = px;
rank[px]++;
}
components--;
return true;
}
bool isConnected() {
return components == 1;
}
};
int main() {
int n, m;
cin >> n >> m;
vector<Edge> edges;
UnionFind uf(n);
for (int i = 0; i < m; i++) {
int x, y, z, p;
cin >> x >> y >> z >> p;
int cost = (p == 1) ? 0 : z;
edges.push_back({x, y, cost});
// 如果已经连接,先在并查集中合并
if (p == 1) {
uf.unite(x, y);
}
}
sort(edges.begin(), edges.end());
int totalCost = 0;
for (const Edge& edge : edges) {
if (uf.unite(edge.u, edge.v)) {
totalCost += edge.cost;
}
}
if (uf.isConnected()) {
cout << totalCost << endl;
} else {
cout << -1 << endl;
}
return 0;
}
解法二:Prim算法(基于顶点的贪心算法)
Prim算法的核心思想是:
- 从任意顶点开始
- 每次选择连接已访问顶点和未访问顶点的最小权重边
- 直到所有顶点都被访问
Java实现
import java.util.*;
public class Solution2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
// 构建邻接表
List<List<int[]>> graph = new ArrayList<>();
for (int i = 0; i <= n; i++) {
graph.add(new ArrayList<>());
}
// 并查集处理已连接的边
UnionFind uf = new UnionFind(n);
for (int i = 0; i < m; i++) {
int x = sc.nextInt();
int y = sc.nextInt();
int z = sc.nextInt();
int p = sc.nextInt();
int cost = (p == 1) ? 0 : z;
graph.get(x).add(new int[]{y, cost});
graph.get(y).add(new int[]{x, cost});
if (p == 1) {
uf.union(x, y);
}
}
// 使用Prim算法
boolean[] visited = new boolean[n + 1];
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]);
// 从节点1开始
visited[1] = true;
for (int[] edge : graph.get(1)) {
pq.offer(new int[]{edge[0], edge[1]});
}
int totalCost = 0;
int edgesUsed = 0;
while (!pq.isEmpty() && edgesUsed < n - 1) {
int[] current = pq.poll();
int node = current[0];
int cost = current[1];
if (visited[node]) continue;
visited[node] = true;
totalCost += cost;
edgesUsed++;
for (int[] edge : graph.get(node)) {
if (!visited[edge[0]]) {
pq.offer(new int[]{edge[0], edge[1]});
}
}
}
// 检查是否所有节点都被访问
boolean allConnected = true;
for (int i = 1; i <= n; i++) {
if (!visited[i]) {
allConnected = false;
break;
}
}
if (allConnected) {
System.out.println(totalCost);
} else {
System.out.println(-1);
}
}
static class UnionFind {
int[] parent, rank;
public UnionFind(int n) {
parent = new int[n + 1];
rank = new int[n + 1];
for (int i = 1; i <= n; i++) {
parent[i] = i;
}
}
public int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
public boolean union(int x, int y) {
int px = find(x), py = find(y);
if (px == py) return false;
if (rank[px] < rank[py]) {
parent[px] = py;
} else if (rank[px] > rank[py]) {
parent[py] = px;
} else {
parent[py] = px;
rank[px]++;
}
return true;
}
}
}
Python实现
import heapq
from collections import defaultdict
def solve_prim():
n = int(input())
m = int(input())
# 构建邻接表
graph = defaultdict(list)
for _ in range(m):
x, y, z, p = map(int, input().split())
cost = 0 if p == 1 else z
graph[x].append((y, cost))
graph[y].append((x, cost))
# 使用Prim算法
visited = [False] * (n + 1)
min_heap = []
# 从节点1开始
visited[1] = True
for neighbor, cost in graph[1]:
heapq.heappush(min_heap, (cost, neighbor))
total_cost = 0
edges_used = 0
while min_heap and edges_used < n - 1:
cost, node = heapq.heappop(min_heap)
if visited[node]:
continue
visited[node] = True
total_cost += cost
edges_used += 1
for neighbor, edge_cost in graph[node]:
if not visited[neighbor]:
heapq.heappush(min_heap, (edge_cost, neighbor))
# 检查是否所有节点都被访问
all_connected = all(visited[i] for i in range(1, n + 1))
if all_connected:
print(total_cost)
else:
print(-1)
solve_prim()
C++实现
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
// 构建邻接表
vector<vector<pair<int, int>>> graph(n + 1);
for (int i = 0; i < m; i++) {
int x, y, z, p;
cin >> x >> y >> z >> p;
int cost = (p == 1) ? 0 : z;
graph[x].push_back({y, cost});
graph[y].push_back({x, cost});
}
// 使用Prim算法
vector<bool> visited(n + 1, false);
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
// 从节点1开始
visited[1] = true;
for (auto& edge : graph[1]) {
pq.push({edge.second, edge.first});
}
int totalCost = 0;
int edgesUsed = 0;
while (!pq.empty() && edgesUsed < n - 1) {
auto current = pq.top();
pq.pop();
int cost = current.first;
int node = current.second;
if (visited[node]) continue;
visited[node] = true;
totalCost += cost;
edgesUsed++;
for (auto& edge : graph[node]) {
if (!visited[edge.first]) {
pq.push({edge.second, edge.first});
}
}
}
// 检查是否所有节点都被访问
bool allConnected = true;
for (int i = 1; i <= n; i++) {
if (!visited[i]) {
allConnected = false;
break;
}
}
if (allConnected) {
cout << totalCost << endl;
} else {
cout << -1 << endl;
}
return 0;
}
算法复杂度分析
Kruskal算法:
- 时间复杂度:O(M log M),主要是排序的复杂度
- 空间复杂度:O(N + M)
Prim算法:
- 时间复杂度:O(M log N),使用优先队列的复杂度
- 空间复杂度:O(N + M)
总结
两种算法都能有效解决这个5G网络建设问题:
- Kruskal算法更适合边数较少的稀疏图,实现相对简单
- Prim算法更适合边数较多的稠密图,在节点数较少时表现更好
对于这道题目,由于N ≤ 20,两种算法的性能差异不大,都能很好地解决问题。关键是要正确处理已存在的光纤连接(成本为0)和判断图的连通性。