一. 题目-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
示例
示例1
输入:3
3
1 2 3 0
1 3 1 0
2 3 5 0
输出:4
说明:只需要在1,2以及2,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
二.解题思路
这个问题可以通过最小生成树算法来解决,其中最常用的算法之一是Kruskal算法。以下是解题思路:
-
初始化一个并查集(Union-Find)用于维护基站之间的连接关系,初始时每个基站都是一个独立的集合。
-
将已有的光纤连接(已存在光纤连接的)加入并查集,表示它们已经联通。
-
对未连接的基站对按照光纤成本进行升序排序。
-
依次遍历排序后的基站对,如果两个基站不在同一集合中,则将它们连接,并将连接的成本累加到结果中。
-
继续遍历直到所有基站都联通或者所有可能的连接都尝试完毕。
-
如果最终连接的基站数量等于总基站数量减一,则说明成功建设互联互通的5G网络,输出累计的最小成本;否则,输出-1。
这种方法通过贪心地选择成本最小的连接,确保了最终建设的网络成本最小。
三.题解代码
Python题解代码
class UF:
def __init__(self, size):
self.root = [i for i in range(size+1)]
self.rank = [1] * (size+1)
self.count = 0
def find(self, x):
if x == self.root[x]:
return x
self.root[x] = self.find(self.root[x])
return self.root[x]
def union(self, x, y):
self.root[self.find(x)] = self.find(y)
self.count += 1
def get_count(self):
return self.count
if __name__ == "__main__":
n, m = map(int, input().split())
uf = UF(n)
networks = []
for _ in range(m):
input_str = input()
nums = list(map(int, input_str.split()))
if nums[3] == 1:
if uf.find(nums[0]) != uf.find(nums[1]):
uf.union(nums[0], nums[1])
else:
networks.append(nums)
networks.sort(key=lambda x: x[2])
result = 0
i = 0
while True:
if i >= len(networks):
break
else:
if uf.find(networks[i][0]) != uf.find(networks[i][1]):
uf.union(networks[i][0], networks[i][1])
result += networks[i][2]
if uf.get_count() == n - 1:
break
i += 1
if uf.get_count() != n - 1:
result = -1
print(result)
JAVA题解代码
import java.util.*;
class Main {
static int[] fa = new int[200];
static List<Integer[]> a = new ArrayList<>();
// 合并两个集合,返回是否成功合并
public static int merge(int x , int y){
int fx = getfa(x);
int fy = getfa(y);
if (fx == fy) return 0;
fa[fx] = fy;
return 1;
}
// 使用路径压缩找到节点x的根节点
public static int getfa(int x){
if (fa[x] == x) return x;
return fa[x] = getfa(fa[x]);
}
public static void init (){
// 初始化并查集
for (int i = 1 ; i < fa.length ; i++){
fa[i] = i;
}
}
public static void main (String [] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 读取节点数
int m = sc.nextInt(); // 读取边数
init();
for (int i = 1 ; i <= m ; i++){
int x = sc.nextInt(); // 边的起始节点
int y = sc.nextInt(); // 边的结束节点
int z = sc.nextInt(); // 边的权重
int p = sc.nextInt(); // 是否预先连接的标志
if (p == 1) merge(x , y); // 如果标志为1,直接合并两个节点
else {
a.add(new Integer[]{x , y , z}); // 否则将边加入到边列表中
}
}
// 按照边的权重进行排序
a.sort((a , b) -> {
return a[2].compareTo(b[2]);
});
int ans = 0;
for (Integer[] x : a){
if (merge(x[0] , x[1]) == 1) ans += x[2]; // 如果成功合并两个节点,累加权重
}
// 判断所有节点是否联通
boolean ok = true;
for(int i = 1 ; i <= n ; i++) {
if(getfa(i) != getfa(1)) ok = false;
}
if (ok) {
System.out.println(ans); // 如果所有节点联通,输出最小花费
}
else {
System.out.println(-1); // 否则输出-1
}
return ;
}
}
C/C++题解代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class UF {
vector<int> root;
vector<int> rank;
int count;
public:
UF(int size) {
root.resize(size + 1);
rank.resize(size + 1);
count = 0;
for (int i = 0; i < size + 1; i++) {
root[i] = i;
rank[i] = 1;
}
}
int find(int x) {
if (x == root[x]) {
return x;
}
return root[x] = find(root[x]);
}
void union(int x, int y) {
root[find(x)] = find(y);
count += 1;
}
int get_count() {
return count;
}
};
int main() {
int n, m;
cin >> n >> m;
cin.ignore();
UF uf(n);
vector<vector<int>> networks;
for (int i = 0; i < m; i++) {
string input_str;
getline(cin, input_str);
stringstream ss(input_str);
vector<int> nums;
int num;
while (ss >> num) {
nums.push_back(num);
}
if (nums[3] == 1) {
if (uf.find(nums[0]) != uf.find(nums[1])) {
uf.union(nums[0], nums[1]);
}
} else {
networks.push_back({nums[0], nums[1], nums[2]});
}
}
sort(networks.begin(), networks.end(), [](const vector<int>& a, const vector<int>& b) {
return a[2] < b[2];
});
int result = 0;
int i = 0;
while (true) {
if (i >= networks.size()) {
break;
} else {
if (uf.find(networks[i][0]) != uf.find(networks[i][1])) {
uf.union(networks[i][0], networks[i][1]);
result += networks[i][2];
if (uf.get_count() == n - 1) {
break;
}
}
}
i += 1;
}
if (uf.get_count() != n - 1) {
result = -1;
}
cout << result << endl;
return 0;
}
JS题解代码
process.stdin.resume();
process.stdin.setEncoding('utf-8');
let input = '';
process.stdin.on('data', (data) => {
input += data;
});
process.stdin.on('end', () => {
const lines = input.trim().split('\n');
// write your code here!
const fa = Array.from({ length: 200 }, (_, i) => i);
const a = [];
function getfa(x) {
if (fa[x] == x) {
return x;
}
fa[x] = getfa(fa[x]);
return fa[x];
}
// 合并两个集合,返回是否成功合并
function merge(x, y) {
const fx = getfa(x);
const fy = getfa(y);
if (fx == fy) {
return 0;
}
fa[fx] = fy;
return 1;
}
function init() {
// 初始化并查集
for (let i = 1; i < 200; i++) {
fa[i] = i;
}
}
const n = Number(lines[0]);
const m = Number(lines[1]);
init();
for (let i = 1; i <= m; i++) {
const [x, y, z, p] = lines[i + 1].trim().split(' ').map(Number); // 读取边的起始节点、结束节点、权重和标志
if (p === 1) {
merge(x, y); // 如果标志为1,直接合并两个节点
} else {
a.push([x, y, z]); // 否则将边加入边列表
}
}
// 按边的权重排序
a.sort((a, b) => a[2] - b[2]);
let ans = 0;
for (const x of a) {
if (merge(x[0], x[1]) == 1) {
ans += x[2]; // 如果成功合并两个节点,累加权重
}
}
// 判断所有节点是否联通
let ok = true;
for (let i = 1; i <= n; i++) {
if (getfa(i) !== getfa(1)) {
ok = false;
break;
}
}
if (ok) {
console.log(ans); // 如果所有节点联通,输出最小花费
} else {
console.log(-1); // 否则输出-1
}
});
代码OJ评判结果
通过测试点
四.代码讲解(Java&Python&C++&JS分别讲解)
Python题解代码解析:
-
UF类的定义: 定义了一个并查集(Union-Find)类,包括构造函数
__init__
用于初始化并查集,find
用于查找节点所属的集合的根节点,union
用于合并两个集合,get_count
用于获取当前集合的数量。 -
主程序部分:
- 通过输入读取基站的个数
n
和具备光纤直连条件的基站对的数目m
。 - 创建并初始化并查集
uf
,表示每个基站初始时独立成一个集合。 - 通过循环读取基站对的信息,如果已存在光纤连接,则直接在并查集中进行合并操作;否则,将基站对的信息添加到
networks
列表中。 - 对
networks
列表按照光纤成本进行升序排序。 - 通过循环遍历排序后的基站对,如果两个基站不在同一集合中,则将它们连接,并累加连接的成本。直到所有基站都联通或者所有可能的连接都尝试完毕。
- 如果最终连接的基站数量等于总基站数量减一,则说明成功建设互联互通的5G网络,输出累计的最小成本;否则,输出-1。
- 通过输入读取基站的个数
JAVA题解代码解析:
-
全局变量定义: 定义了一个静态数组
fa
用于存储每个节点的父节点,一个列表a
用于存储未连接的边的信息。 -
merge方法: 合并两个集合的方法,返回是否成功合并。
-
getfa方法: 使用路径压缩找到节点 x 的根节点。
-
init方法: 初始化并查集,将每个节点的父节点设为自己。
-
主程序部分:
- 通过输入读取节点数
n
和边数m
。 - 初始化并查集。
- 循环读取边的信息,如果预先连接标志为1,则直接合并两个节点;否则,将边的信息加入列表
a
。 - 对列表
a
按照边的权重进行排序。 - 遍历排序后的边,如果成功合并两个节点,则累加权重。
- 判断所有节点是否联通,如果是则输出最小花费,否则输出-1。
- 通过输入读取节点数
C/C++题解代码解析:
-
UF类的定义: 与Python中相同,定义了一个并查集(Union-Find)类。
-
主程序部分:
- 通过输入读取基站的个数
n
和具备光纤直连条件的基站对的数目m
。 - 创建并初始化并查集
uf
。 - 循环读取基站对的信息,如果已存在光纤连接,则直接在并查集中进行合并操作;否则,将基站对的信息添加到
networks
列表中。 - 对
networks
列表按照光纤成本进行升序排序。 - 通过循环遍历排序后的基站对,如果两个基站不在同一集合中,则将它们连接,并累加连接的成本。直到所有基站都联通或者所有可能的连接都尝试完毕。
- 如果最终连接的基站数量等于总基站数量减一,则说明成功建设互联互通的5G网络,输出累计的最小成本;否则,输出-1。
- 通过输入读取基站的个数
JS题解代码解析:
-
全局变量定义: 定义了一个数组
fa
用于存储每个节点的父节点,一个列表a
用于存储未连接的边的信息。 -
merge方法: 合并两个集合的方法,返回是否成功合并。
-
getfa方法: 使用路径压缩找到节点 x 的根节点。
-
init方法: 初始化并查集,将每个节点的父节点设为自己。
-
主程序部分:
- 通过输入读取节点数
n
和边数m
。 - 初始化并查集。
- 循环读取边的信息,如果预先连接标志为1,则直接合并两个节点;否则,将边的信息加入列表
a
。 - 对列表
a
按照边的权重进行排序。 - 遍历排序后的边,如果成功合并两个节点,则累加权重。
- 判断所有节点是否联通,如果是则输出最小花费,否则输出-1。
- 通过输入读取节点数