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~

posted @ 2019-04-22 19:57  暗い之殇  阅读(366)  评论(0编辑  收藏  举报