华为OD机试(C卷+D卷)2024真题目录(Java & c++ & python)
题目描述
现需要在某城市进行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
用例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
解题思路
典型的最小生成树问题。
下面图示为最小生成树的相关知识和两个经典算法解析,如果比较熟练了可以直接跳过看代码。
本题解析
本题属于最小生成树的变种题,区别于板子题,本题中主要是存在一些已经关联好的节点。
只需要预处理提前加入,或者将这些边权设为0,即可达到目的。
将边权设为0,则可以直接套模板,更方便些。
C++、Java、Python代码如下:
C++参考代码
Prim算法
#include <bits/stdc++.h>
using namespace std;
#define MAX_N 22
int n;
int graph[MAX_N][MAX_N];
int prim() {
// 记录最小生成树的总权重
int minWeight = 0;
// inTree[i] 表示节点 i 是否已经纳入最小生成树
bool inTree[MAX_N] = {false};
// 从节点 1 开始构建最小生成树
inTree[1] = true;
int treeSize = 1;
// dis[i] 表示节点 i 到当前最小生成树的最短距离
int dis[MAX_N];
for (int i = 1; i <= n; i++) {
dis[i] = graph[1][i];
}
// 当最小生成树的节点数量达到 n 时结束循环
while (treeSize < n) {
int minDis = INT_MAX;
int nodeIdx = 0;
// 找到距离最小生成树最近的未纳入树中的节点
for (int i = 1; i <= n; i++) {
if (!inTree[i] && dis[i] < minDis) {
minDis = dis[i];
nodeIdx = i;
}
}
// 如果没有找到有效节点,则说明图不连通,无法形成最小生成树
if (nodeIdx == 0) {
return -1;
}
// 将选中的节点纳入最小生成树
inTree[nodeIdx] = true;
treeSize++;
minWeight += dis[nodeIdx];
// 更新其他节点到最小生成树的最短距离
for (int i = 1; i <= n; i++) {
if (!inTree[i] && graph[nodeIdx][i] < dis[i]) {
dis[i] = graph[nodeIdx][i];
}
}
}
return minWeight;
}
int main() {
cin >> n; // 输入节点数量
int m;
cin >> m; // 输入边的数量
// 初始化邻接矩阵
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
graph[i][j] = INT_MAX;
}
}
// 输入边的信息
for (int i = 0; i < m; i++) {
int x, y, z, p;
cin >> x >> y >> z >> p;
if (p == 0) {
graph[x][y] = z;
graph[y][x] = z;
} else {
graph[x][y] = 0;
graph[y][x] = 0;
}
}
cout << prim() << endl;
return 0;
}
Kruskal算法
#include <bits/stdc++.h>
using namespace std;
// 并查集实现
class UnionFindSet {
public:
int *fa;
int count;
explicit UnionFindSet(int n) {
fa = new int[n];
count = n;
for (int i = 0; i < n; i++) fa[i] = i;
}
int find(int x) {
if (x != fa[x]) {
fa[x] = find(fa[x]);
}
return fa[x];
}
void merge(int x, int y) {
int x_fa = find(x);
int y_fa = find(y);
if (x_fa != y_fa) {
fa[y_fa] = x_fa;
count--;
}
}
};
// 边的定义
class Edge {
public:
int from, to, weight;
Edge(int from, int to, int weight) : from(from), to(to), weight(weight) {}
};
int n;
vector<Edge> edges;
int kruskal() {
int minWeight = 0;
// 按权重升序排序
sort(edges.begin(), edges.end(), [](Edge &a, 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)) {
minWeight += edge.weight;
ufs.merge(edge.from, edge.to);
// 判断是否形成了最小生成树
if (ufs.count == 2) {
return minWeight;
}
}
}
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;
}
Java参考代码
Prim算法
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 基站数量(节点数)
int m = sc.nextInt(); // 基站对数量(边数)
// 初始化邻接矩阵,表示各点之间的边权
int[][] graph = new int[n + 1][n + 1];
for (int i = 1; i <= n; i++) {
Arrays.fill(graph[i], Integer.MAX_VALUE);
}
for (int i = 0; i < m; i++) {
int u = sc.nextInt();
int v = sc.nextInt();
int weight = sc.nextInt();
int status = sc.nextInt();
if (status == 0) {
// u-v 边的权重为 weight
graph[u][v] = weight;
graph[v][u] = weight;
} else {
// u-v 边已经联通,权重为 0
graph[u][v] = 0;
graph[v][u] = 0;
}
}
System.out.println(prim(graph, n));
}
public static int prim(int[][] graph, int n) {
int totalWeight = 0; // 记录最小生成树的总权重
boolean[] inTree = new boolean[n + 1]; // 标记节点是否已加入最小生成树
int[] minDist = new int[n + 1]; // 记录每个节点到最小生成树的最短距离
Arrays.fill(minDist, Integer.MAX_VALUE);
minDist[1] = 0; // 从节点1开始构造最小生成树
for (int i = 1; i <= n; i++) {
int closestNode = -1;
for (int j = 1; j <= n; j++) {
if (!inTree[j] && (closestNode == -1 || minDist[j] < minDist[closestNode])) {
closestNode = j;
}
}
if (minDist[closestNode] == Integer.MAX_VALUE) {
return -1; // 图不连通,无法生成最小生成树
}
inTree[closestNode] = true; // 将最小距离的节点加入最小生成树
totalWeight += minDist[closestNode];
for (int j = 1; j <= n; j++) {
if (!inTree[j] && graph[closestNode][j] < minDist[j]) {
minDist[j] = graph[closestNode][j];
}
}
}
return totalWeight;
}
}
Kruskal算法
import java.util.Arrays;
import java.util.Scanner;
public class Main {
// 边类,表示图中的一条边
static class Edge {
int start; // 边的起点
int end; // 边的终点
int weight; // 边的权重
public Edge(int start, int end, int weight) {
this.start = start;
this.end = end;
this.weight = weight;
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int numStations = sc.nextInt(); // 基站数量(节点数)
int numConnections = sc.nextInt(); // 基站之间的连接数量(边数)
Edge[] edges = new Edge[numConnections];
for (int i = 0; i < numConnections; i++) {
int x = sc.nextInt();
int y = sc.nextInt();
int z = sc.nextInt();
int p = sc.nextInt();
// 如果 p == 1,表示这条边可以视为权重为0
edges[i] = new Edge(x, y, p == 0 ? z : 0);
}
System.out.println(findMinimumSpanningTree(edges, numStations));
}
public static int findMinimumSpanningTree(Edge[] edges, int numStations) {
int totalWeight = 0;
// 按照边的权重从小到大排序
Arrays.sort(edges, (a, b) -> a.weight - b.weight);
UnionFind unionFind = new UnionFind(numStations + 1);
// 遍历所有边,从权重最小的边开始
for (Edge edge : edges) {
// 如果两个节点不在同一个连通分量,则合并它们,并将该边加入最小生成树
if (unionFind.find(edge.start) != unionFind.find(edge.end)) {
totalWeight += edge.weight;
unionFind.union(edge.start, edge.end);
// 检查并查集中是否只剩下两个连通分量(一个是无用的索引0)
if (unionFind.count == 2) {
return totalWeight;
}
}
}
return -1; // 如果没有形成最小生成树,返回-1
}
}
// 并查集类,用于管理节点的连通性
class UnionFind {
int[] parent;
int count;
public UnionFind(int size) {
this.parent = new int[size];
this.count = size;
for (int i = 0; i < size; i++) {
this.parent[i] = i;
}
}
public int find(int x) {
if (x != this.parent[x]) {
this.parent[x] = find(this.parent[x]);
}
return this.parent[x];
}
public void union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
this.parent[rootY] = rootX;
this.count--;
}
}
}
Python参考代码
Prim算法
import sys
# 输入获取
n = int(input()) # 基站数量(节点数)
m = int(input()) # 基站对数量(边数)
# 邻接矩阵, 初始化默认各点之间互不联通,即边权为无限大
graph = [[sys.maxsize for _ in range(n + 1)] for _ in range(n + 1)]
for _ in range(m):
x, y, z, p = map(int, input().split())
if p == 0:
# x-y 边权为 z
graph[x][y] = z
graph[y][x] = z
else:
# 已经联通的两点,边权设为 0
graph[x][y] = 0
graph[y][x] = 0
# Prim 算法
def prim():
# 记录最小生成树的边权和
totalWeight = 0
# inMST[i] 表示节点 i 是否在最小生成树中
inMST = [False] * (n + 1)
# 初始时任选一个节点作为最小生成树的初始节点,这里选择节点 1
inMST[1] = True
mstNodeCount = 1 # 记录最小生成树中点的数量
# dist[i] 表示节点 i 到最小生成树集合的最短距离
dist = [graph[1][i] for i in range(n + 1)]
# 当最小生成树中的点数小于 n 时继续循环
while mstNodeCount < n:
# 找到未纳入最小生成树的节点中距离最小生成树最近的节点
minDist = sys.maxsize
closestNode = 0
for i in range(1, n + 1):
if not inMST[i] and dist[i] < minDist:
minDist = dist[i]
closestNode = i
# 若无法找到这样的节点,说明图不连通,无法形成最小生成树
if closestNode == 0:
return -1
# 将最近的节点纳入最小生成树
inMST[closestNode] = True
mstNodeCount += 1
totalWeight += dist[closestNode]
# 更新其他节点到最小生成树的距离
for i in range(1, n + 1):
if not inMST[i] and graph[closestNode][i] < dist[i]:
dist[i] = graph[closestNode][i]
return totalWeight
# 输出结果
print(prim())
Kruskal算法
# 并查集实现
class UnionFindSet:
def __init__(self, n):
self.parent = list(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):
rootX = self.find(x)
rootY = self.find(y)
if rootX != rootY:
self.parent[rootY] = rootX
self.count -= 1
# 输入获取
n = int(input()) # 基站数量(节点数)
m = int(input()) # 基站对数量(边数)
edges = []
for _ in range(m):
x, y, z, p = map(int, input().split())
if p == 0:
edges.append([x, y, z])
else:
edges.append([x, y, 0])
# Kruskal算法
def kruskal():
minWeight = 0
edges.sort(key=lambda edge: edge[2]) # 按照边权升序排序
ufs = UnionFindSet(n + 1)
for x, y, z in edges:
if ufs.find(x) != ufs.find(y):
minWeight += z
ufs.union(x, y)
if ufs.count == 2: # 所有节点合并为一个连通分量
return minWeight
return -1 # 无法形成最小生成树
# 算法入口
print(kruskal())