P3366 【模板】最小生成树
原题链接 https://www.luogu.org/problemnew/show/P3366
一道最小生成树的模板题......
昨天刚学最小生成树,wz大佬讲的一塌糊涂井然有序,所以我们今天做起板子题来一脸懵逼游刃有余.....
老师让wz大佬讲Prim算法,大佬竟然说不会.......于是给我们讲起了Kruskal算法,结果老师让我们用Prim算法解........
话不多说,讲下Kruskal算法 ,要用到并查集 (且用到贪心思想)Prim算法被我吃辣,滑稽 :
对于任意一个连通网的最小生成树来说,在要求总的权值最小的情况下,最直接的想法就是将连通网中的所有边按照权值大小进行升序排序,从小到大依次选择。
由于最小生成树本身是一棵生成树,所以需要时刻满足以下两点:
- 生成树中任意顶点之间有且仅有一条通路,也就是说,生成树中不能存在回路;
- 对于具有 n 个顶点的连通网,其生成树中只能有 n-1 条边,这 n-1 条边连通着 n 个顶点。
连接 n 个顶点在不产生回路的情况下,只需要 n-1 条边。
所以克鲁斯卡尔算法的具体思路是:将所有边按照权值的大小进行升序排序,然后从小到大一一判断,条件为:如果这个边不会与之前选择的所有边组成回路,就可以作为最小生成树的一部分;反之,舍去。直到具有 n 个顶点的连通网筛选出来 n-1 条边为止。筛选出来的边和所有的顶点构成此连通网的最小生成树。
判断是否会产生回路的方法为:在初始状态下给每个顶点赋予不同的标记,对于遍历过程的每条边,其都有两个顶点,判断这两个顶点的标记是否一致,如果一致,说明它们本身就处在一棵树中,如果继续连接就会产生回路;如果不一致,说明它们之间还没有任何关系,可以连接。
假设遍历到一条由顶点 A 和 B 构成的边,而顶点 A 和顶点 B 标记不同,此时不仅需要将顶点 A 的标记更新为顶点 B 的标记,还需要更改所有和顶点 A 标记相同的顶点的标记,全部改为顶点 B 的标记。
下面上代码:
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; int n,m,fa[5001],tot=0,sum=0; //tot记录最短路径,sum记录边数 struct point { int from; //一条边的始点 int to; //一条边的终点 int dis; //一条边的权值 }a[200001]; int cmp(const point &x,const point &y) //自定义排序,按权值大小排序,好进行下面的贪心算法 { return x.dis<y.dis; } int getf(int x) //找父结点 { if(fa[x]!=x) fa[x]=getf(fa[x]); //进一步找出父结点的父结点,也就是祖先结点 return fa[x]; //返回父结点 } void father(int x,int y) { int fx=getf(x); //找出x的父结点 int fy=getf(y); //找出y的父结点 if(fx!=fy) fa[fx]=fa[fy]; //如果两个结点的父结点不同,则弄成相同的,证明在同一棵树上 } int main() { cin>>n>>m; for(int i=1;i<=m;i++) //n条边 cin>>a[i].from>>a[i].to>>a[i].dis; for(int i=1;i<=n;i++) fa[i]=i; //一开始每个结点都可以看作是一颗独立的树,那么该结点的父结点只能是它自己 sort(a+1,a+1+m,cmp); //自定义按照权值大小排序 for(int i=1;i<=m;i++) { if(getf(a[i].from)!=getf(a[i].to)) //如果父结点不同,也就是不在同一个树上,进而不能构成回环,则可以贪心计算上 { tot+=a[i].dis; //加上该边的权值 father(a[i].from,a[i].to); //将两个结点标记为同一父结点 sum++; //边数+1 } if(sum==n-1) break; //对于n个结点的图,最小生成树只需要n-1条边就够了,如果边多了就不能保证是最优解了 } cout<<tot; //输出最小生成树的值 return 0; }
完结撒花QAQ~