今天做的第一道题目是最小生成树,因为是模板题所以很简单,就是套模板。
题目如下:
【模板】最小生成树
题目描述
如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz
。
输入格式
第一行包含两个整数 N,MN,M,表示该图共有 NN 个结点和 MM 条无向边。
接下来 MM 行每行包含三个整数 X_i,Y_i,Z_iXi,Yi,Zi,表示有一条长度为 Z_iZi 的无向边连接结点 X_i,Y_iXi,Yi。
输出格式
如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz
。
输入输出样例
输入 #1复制
4 5 1 2 2 1 3 2 1 4 3 2 3 4 3 4 3
输出 #1复制
7
说明/提示
数据规模:
对于 20\%20% 的数据,N\le 5N≤5,M\le 20M≤20。
对于 40\%40% 的数据,N\le 50N≤50,M\le 2500M≤2500。
对于 70\%70% 的数据,N\le 500N≤500,M\le 10^4M≤104。
对于 100\%100% 的数据:1\le N\le 50001≤N≤5000,1\le M\le 2\times 10^51≤M≤2×105,1\le Z_i \le 10^41≤Zi≤104。
样例解释:
所以最小生成树的总边权为 2+2+3=72+2+3=7。
分析:
1,刚开始看这个题目,我是没怎么看明白的,所以我去了哔站看最小生成树的图解,就很清晰了。
2,首先最小生成树大致有两种算法,分别是Kruskal(克鲁斯卡尔)和Prim(普里姆),我个人觉得Kruskal(克鲁斯卡尔)是要比Prim(普里姆)相对来说简单一点的。
3,那在这个题目里面我选择使用Kruskal(克鲁斯卡尔),那Kruskal(克鲁斯卡尔)的主要思想是什么呢?首先,先按照边的长度从小到大排序,然后使用并查集合并并且查找连接这点后会不会形成一个环,如果形成了一个闭合环,那就不要这点,并且继续向下查找,如果没有形成一个闭合环,那么就加入这点,成为最小生成树的一部分。
4,那么什么样才算结束呢?那就是当我加入的树的点达到了总点数减1的时候就退出,这个时候就已经形成了最小生成树。如果加入的树的点达不到总点数减1,那就代表形成不了最小生成树,就输出orz。
代码如下:
#include<stdio.h>
#include<math.h>
#include<string.h>
int pre[300000],k;
int find(int d)
{
return pre[d]==0?d:(pre[d] = find(pre[d]));
}
void join(int b, int d) {
int fx = find(d);
int fy = find(b);
if (fx!=fy){
pre[fx] = fy;
}
}
struct zx
{
int x;
int y;
int z;
}a[200005];
void kp(int begin, int end){
if(begin > end)
return;
struct zx tmp = a[begin];
int i = begin;
int j = end;
while(i != j){
while(a[j].z>=tmp.z&& j > i)
j--;
while(a[i].z<=tmp.z&& j > i)
i++;
if(j > i){
struct zx t = a[i];
a[i] = a[j];
a[j] = t;
}
}
a[begin] = a[i];
a[i] = tmp;
kp(begin, i-1);
kp(i+1, end);
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
}
kp(0,m-1);
int sum=0;
for(int i=0;i<m;i++)
{
if(find(a[i].x)==find(a[i].y))
{continue;}
k++;
pre[find(a[i].x)]=find(a[i].y);
sum+=a[i].z;
if(k+1==n) break;
}
if(k+1==n)
printf("%d",sum);
else
printf("orz");
}
总结:
今天干掉了Kruskal(克鲁斯卡尔),对于这个小算法,我已经没有什么疑问了,至于prim,我也已经了解了它的基本思想,只需要做题巩固,万般了解不如做题来得痛快,那今天主要就是最小生成树的理解以及两个算法的学习及巩固。