2024华为OD笔试机试 - 5G网络建设 (python/c++/java D卷C卷真题算法)

华为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())

  • 9
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

算法之旅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值