一、问题描述
小蓝国是一个水上王国,有2021
个城邦,依次编号1
到2021
。在任意两个城邦之间,都有一座桥直接连接。
为了庆祝小蓝国的传统节日,小蓝国政府准备将一部分桥装饰起来。
对于编号为a
和b
的两个城邦,它们之间的桥如果要装饰起来,需要的费用如下计算:找到a
和b
在十进制下所有不同的数位,将数位上的数字求和。
例如,编号为2021
和922
两个城邦之间,千位、百位和个位都不同,将这些数位上的数字加起来是 (2 + 0 + 1) + (0 + 9 + 2) = 14。注意922
没有千位,千位看成0
。
为了节约开支,小蓝国政府准备只装饰2020
座桥,并且要保证从任意一个城邦到任意另一个城邦之间可以完全只通过装饰的桥到达。
请问,小蓝国政府 至少要花多少费用才能完成装饰。
提示:建议使用计算机编程解决问题。
二、问题分析
这道题就最小生成树的题目,构造最小生成树有两种算法:Prim(普里姆)算法和Kruskal(克鲁斯卡尔)算法。本题采用并查集来实现Kruskal算法,而桥的装饰费用get函数来计算。
Kruskal算法:按权值的递增次序选择合适的边来古构造最小生成树的方法。
三、代码实现
// 编程软件:Dev-C++ 5.4.0
#include<iostream>
#include<algorithm>
using namespace std;
const int N=2030, M=N*N/2, n=2021;
int m=0; // 记录桥的数量
int p[N]; // 并查集
// 存储桥相关信息
struct edge{
int a,b,c; // a,b为城邦编号,c为装饰桥的费用
bool operator <(const edge& e) const{ // 重载运算符“<”
return c<e.c;
}
}e[M];
// 找到 i 和 j 在十进制下所有不同的数位,将数位上的数字求和
int get(int i,int j){
int res=0;
while(i||j){
int a=i%10,b=j%10;
if(a!=b) res=res+a+b;
i/=10,j/=10;
}
return res;
}
// 寻找x所在的集合,并返回x的根节点
int find(int x){
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
int main(){
// 初始化并查集
for(int i=1;i<=n;i++) p[i]=i;
// 计算所有桥的装饰费用,并存储在e数组中
for(int i=1; i<=n; i++){
for(int j=i+1; j<=n; j++){
edge t;
t.a=i, t.b=j, t.c=get(i,j);
e[m++]=t;
}
}
sort(e,e+m); // 按照桥的装饰费用从小到大排序
int res=0;
for(int i=0; i<m; i++){
int a=e[i].a, b=e[i].b, c=e[i].c;
// a和 b不是同一个集合上的,将该桥加入最小生成树
if(find(a)!=find(b)){
res += c;
p[find(a)]=find(b);
}
}
cout << res;
return 0;
}
// 答案: 4046