原题地址:https://www.luogu.org/problem/P3366
题目描述
如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出orz
输入输出格式
输入格式:
第一行包含两个整数N、M,表示该图共有N个结点和M条无向边。(N<=5000,M<=200000)
接下来M行每行包含三个整数Xi、Yi、Zi,表示有一条长度为Zi的无向边连接结点Xi、Yi
输出格式:
输出包含一个数,即最小生成树的各边的长度之和;如果该图不连通则输出orz
输入输出样例
输入样例#1:
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
输出样例#1:
7
说明
时空限制:1000ms,128M
数据规模:
对于20%的数据:N<=5,M<=20
对于40%的数据:N<=50,M<=2500
对于70%的数据:N<=500,M<=10000
对于100%的数据:N<=5000,M<=200000
样例解释:
所以最小生成树的总边权为2+2+3=7
第一种算法待更新
第二种:克鲁斯卡算法—Kruskal算法
Kruskal算法也是一种贪心算法,它是将边按权值排序,每次从剩下的边集中选择权值最小且两个端点不在同一集合的边加入生成树中,反复操作,直到加入了n-1条边。
-
将边按权值从小到大快排。
-
按照权值从小到大依次选边。若当前选取的边加入后使生成树形成环,则舍弃当前边,否则标记当前边并计数。
-
重复第二步操作,直到生成树中包含n-1条边,否则当遍历完所有边后,选取不到n-1条边,表示最小生成树不存在。
代码如下:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
int n,m;
int f[5010]; //数组f[i]表示i的爸爸
int total=0,sum=0; //total记录边长最小和,sum
inline int read(){ //快速读入
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')
f==-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
struct node{ //a,b表示两个点,len表示两个点之间的长度
int a,b,len;
}arr[200050];
bool cmp(node x,node y){ //自定义边长从小到大排序的函数
return x.len<y.len;
}
int find(int k){ //查找x的爸爸,
if(f[k]==k) //如果k的爸爸是自己,他就是总爸爸
return k;
return f[k]=find(f[k]); //否则继续寻找k的爸爸,即路径压缩
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) //把i的爸爸初始化为自己
f[i]=i;
for(int i=1;i<=m;i++)
cin>>arr[i].a>>arr[i].b>>arr[i].len;
sort(arr+1,arr+m+1,cmp); //把每组数按边长从小到大排序
for(int i=1;i<=m;i++){
if(find(arr[i].a)!=find(arr[i].b)){
f[find(arr[i].a)]=find(arr[i].b); //合并集合
total+=arr[i].len; //累加边长
sum++; //记录树的边长
}
if(sum==n-1) //如果树有n-1条边,则跳出循环
break;
}
if(sum==n-1) //如果树满足n-1条边,则输出树最小长度之和
cout<<total<<endl;
else //不符合
cout<<"orz"<<endl;
return 0;
}