zoukankan      html  css  js  c++  java
  • 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~

  • 相关阅读:
    修复PLSQL Developer 与 Office 2010的集成导出Excel 功能
    Using svn in CLI with Batch
    mysql 备份数据库 mysqldump
    Red Hat 5.8 CentOS 6.5 共用 输入法
    HP 4411s Install Red Hat Enterprise Linux 5.8) Wireless Driver
    变更RHEL(Red Hat Enterprise Linux 5.8)更新源使之自动更新
    RedHat 5.6 问题简记
    Weblogic 9.2和10.3 改密码 一站完成
    ExtJS Tab里放Grid高度自适应问题,官方Perfect方案。
    文件和目录之utime函数
  • 原文地址:https://www.cnblogs.com/xcg123/p/10752581.html
Copyright © 2011-2022 走看看