华为OD机试E卷2024真题目录(java & c++ & python)
本人习惯先看输入输出描述,可以明确知道哪些数据已知,需要去得到什么结果,再代入更有目的性地阅读题干内容,快速理解,所以把输入输出描述放在前面,你可以试下这样阅读对你是否有帮助。
输入描述
第一行输入表示基站的个数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
题目描述
现需要在某城市进行5G网络建设,已经选取N个地点设置5G基站,编号固定为1到N,接下来需要各个基站之间使用光纤进行连接以确保基站能互联互通,不同基站之间假设光纤的成本各不相同,且有些节点之间已经存在光纤相连。
请你设计算法,计算出能联通这些基站的最小成本是多少。
注意:基站的联通具有传递性,比如基站A与基站B架设了光纤,基站B与基站C也架设了光纤,则基站A与基站C视为可以互相联通。
用例1
输入
3
3
1 2 3 0
1 3 1 0
2 3 5 0
输出
4
说明 只需要在1,2以及1,3基站之间铺设光纤,其成本为3+1=4
用例2
输入
3
1
1 2 5 0
输出
-1
说明 3基站无法与其他基站连接,输出-1
用例3
输入
3
3
1 2 3 0
1 3 1 0
2 3 5 1
输出
1
说明 2,3基站已有光纤相连,只要在1,3基站之间铺设光纤,其成本为1
解题思路
最小生成树,Prim算法或者Kruskal算法都可以。
区别于板子题,本题中主要是存在一些已经关联好的节点,对于已经关联了的节点,可以认为他们之间的边权为0。这样的话,本题就变成最小生成树的模板题了。
C++、Java、Python参考代码如下:
Java
import java.util.Arrays;
import java.util.Scanner;
public class Main {
// 边类
static class Edge {
int from; // 边的起点
int to; // 边的终点
int weight; // 边的权重
public Edge(int from, int to, int weight) {
this.from = from;
this.to = to;
this.weight = weight;
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt(); // 基站数量(节点数)
int m = scanner.nextInt(); // 基站对数量(边数)
Edge[] edges = new Edge[m];
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,则将x-y边的权重视为0
edges[i] = new Edge(x, y, p == 0 ? z : 0);
}
System.out.println(kruskal(edges, n)); // 输出最小生成树的权重
}
// Kruskal算法实现
public static int kruskal(Edge[] edges, int n) {
int minWeight = 0;
// 按照边权升序排序
Arrays.sort(edges, (a, b) -> a.weight - b.weight);
UnionFindSet ufs = new UnionFindSet(n + 1);
// 遍历所有边
for (Edge edge : edges) {
// 检查是否在同一连通分量
if (ufs.find(edge.from) != ufs.find(edge.to)) {
minWeight += edge.weight; // 累加边的权重
ufs.union(edge.from, edge.to); // 合并连通分量
// 如果只剩下两个连通分量,返回最小生成树的权重
if (ufs.count == 2) {
return minWeight;
}
}
}
return -1; // 如果无法形成最小生成树
}
}
// 并查集类
class UnionFindSet {
int[] fa; // 记录父节点
int count; // 记录连通分量的数量
public UnionFindSet(int n) {
this.fa = new int[n];
this.count = n;
for (int i = 0; i < n; i++) {
this.fa[i] = i; // 初始化每个节点的父节点为自身
}
}
// 查找操作
public int find(int x) {
if (x != this.fa[x]) {
this.fa[x] = this.find(this.fa[x]); // 路径压缩
}
return x;
}
// 合并操作
public void union(int x, int y) {
int xFa = this.find(x);
int yFa = this.find(y);
if (xFa != yFa) {
this.fa[yFa] = xFa; // 合并两个集合
this.count--; // 更新连通分量数量
}
}
}
C++
#include <bits/stdc++.h>
using namespace std;
// 并查集类
class UnionFindSet {
public:
vector<int> parent; // 存储每个节点的父节点
int componentCount; // 连接分量数量
explicit UnionFindSet(int n) {
parent.resize(n);
componentCount = n;
for (int i = 0; i < n; i++) parent[i] = i; // 初始化父节点为自身
}
// 查找节点的根节点,并进行路径压缩
int find(int x) {
if (x != parent[x]) {
parent[x] = find(parent[x]); // 路径压缩
}
return parent[x];
}
// 合并两个节点所在的集合
void merge(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
parent[rootY] = rootX; // 将y的根节点指向x
componentCount--; // 连接分量数量减少
}
}
};
// 边类
class Edge {
public:
int from; // 边的起点
int to; // 边的终点
int weight; // 边的权重
Edge(int from, int to, int weight) : from(from), to(to), weight(weight) {}
};
int n; // 节点数量
vector<Edge> edges; // 边列表
// Kruskal算法实现
int kruskal() {
int totalWeight = 0; // 最小生成树的总权重
// 按边权升序排序
sort(edges.begin(), edges.end(), [](const Edge &a, const Edge &b) {
return a.weight < b.weight;
});
UnionFindSet ufs(n + 1); // 初始化并查集
for (const auto &edge : edges) {
// 检查是否形成环
if (ufs.find(edge.from) != ufs.find(edge.to)) {
totalWeight += edge.weight; // 加入边权
ufs.merge(edge.from, edge.to); // 合并集合
// 如果只剩下两个连通分量,返回结果
if (ufs.componentCount == 2) {
return totalWeight;
}
}
}
return -1; // 无法形成最小生成树
}
int main() {
cin >> n; // 输入节点数量
int m; // 边数量
cin >> m;
for (int i = 0; i < m; i++) {
int x, y, z, p;
cin >> x >> y >> z >> p;
// 如果p==1,则边权为0
edges.emplace_back(x, y, p == 0 ? z : 0);
}
cout << kruskal() << endl; // 输出最小生成树的权重
return 0;
}
Python
# 并查集实现
class UnionFindSet:
def __init__(self, n):
self.parent = [i for i in range(n)] # 初始化父节点
self.count = n # 记录连通分量数量
def find(self, x):
if x != self.parent[x]: # 路径压缩
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
x_root = self.find(x)
y_root = self.find(y)
if x_root != y_root: # 合并不同的连通分量
self.parent[y_root] = x_root
self.count -= 1
# 输入获取
n = int(input()) # 基站数量(节点数)
m = int(input()) # 基站对数量(边数)
edges = []
for _ in range(m):
x, y, z, p = map(int, input().split())
# 如果未关联,添加边权重;如果已关联,权重为0
edges.append([x, y, 0 if p else z])
# Kruskal算法实现
def kruskal():
min_weight = 0
# 按照边权升序排序
edges.sort(key=lambda edge: edge[2])
ufs = UnionFindSet(n + 1) # 创建并查集
for x, y, weight in edges:
# 只有当x和y不在同一连通分量时才能合并
if ufs.find(x) != ufs.find(y):
min_weight += weight
ufs.union(x, y)
# 如果只剩两个连通分量,返回当前最小权重
if ufs.count == 2:
return min_weight
return -1 # 如果无法形成最小生成树
# 算法入口
print(kruskal())