scau 18448 最小生成树

Description

给定结点数为n,边数为m的带权无向连通图G,所有结点编号为1,2,3....n。
求图G的最小生成树的边权和。

输入格式

第一行两个正整数n和m。n,m<=2000
之后的m行,每行三个正整数a,b,w,描述一条连接结点a和b,边权为w的边。1=<a,b<=n,w<=10^18。
注意可能存在重边和自环。

输出格式

一个整数表示图G的最小生成树的边权和(注意用长整型)。

输入样例

7 12
1 2 9
1 5 2
1 6 3
2 3 5
2 6 7
3 4 6
3 7 3
4 5 6
4 7 2
5 6 3
5 7 6
6 7 1

输出样例

16

普里姆算法查找最小生成树的过程,采用了贪心算法的思想。对于包含 N 个顶点的连通网,普里姆算法每次从连通网中找出一个权值最小的边,这样的操作重复 N-1 次,由 N-1 条权值最小的边组成的生成树就是最小生成树。

很好理解的其实!对着代码敲一敲,再在纸上画一画就没问题啦!

邻接矩阵e[2005][2005];标记数组v[2005]用于标记这个点是否走过;距离数组d[2005]用于记录到该点的最短距离,每一次循环需要更新;ans是所求的最短路径

memset(d,127/3,sizeof d);旨在给距离数组d[]赋一个很大的值,我们从顶点1开始走,因此先把v[1]标记上。函数long long prim()中第一个for循环求的是从顶点1到其它点的距离,然后调用GetMin函数,找到距离数组d[]中最小的值,返回其下标,同时将该点做上标记。并把其权值加在ans里。然后!更新距离数组d[]!

以输入样例为例,得到这样一个无向图。

第一个for循环得到的d数组和v数组如下。

 

第一次调用GetMin函数,发现从1到5的距离最短,第一次调用返回下标5,表示我们从1走到了5,为了避免以后再次走到5,我们把5这个点做上标记。现在走的总距离为ans=d[5]=2。我们现在在5这个位置,那么我们需要看看从5出发到其它点的距离会不会比从1出发到相同点的距离更小,所以每一次调用函数后需要更新d数组,如下。

再次调用GetMin函数,发现从5到6(也可以认为是从1到6)的距离最短,返回下标6,并做上标记,此时走的总距离为ans+=d[6]得到ans=5。同上,更新数组d,来看看如果从6出发的话,会不会有更短的路径。

不难发现,从6到7的路径最短,返回下标7,并做上标记,此时的总距离为ans=ans+d[7]=5+1=6。更新数组d。

从7到4的路径最短,返回下标4,并做上标记,此时的总距离为ans=ans+d[4]=6+2=8。更新数组d。

在这时,我们发现,从4出发的话,只能去到3(因为5已经走过了),而从4到3不如从7到3近,所以我们从7到3,返回下标3,并做上标记,此时的ans=8+d[3]=11。再次更新数组d。

只剩下一个2了,所以这道题的最短路径为ans=11+5=16。

最小生成树的样子是这样的

 

其实最小生成树这道题我说的有点啰嗦了,可以不用管从哪个点到哪个点的(但是管了也没事),每次只找最小的路径权值即可,因为最小生成树和最短路径的写法很像,写最短路径的时候就需要知道从哪个点出发到哪个点,再从新的点出发……

下面是全部代码。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>

using namespace std;

long long n,m,v[2005],d[2005],e[2005][2005];

int GetMin()
{
    int i,mini=0;//d[0]很大
    for(i=1;i<=n;i++)
    {
        if(d[mini]>d[i] && v[i]==0)
            mini=i;
    }
    return mini;
}

long long prim()
{
    memset(d,127/3,sizeof d);
    v[1]=1;
    long long ans=0;
    for(int i=1;i<=n;i++)
        d[i]=e[1][i];//顶点1到其它点的距离
    for(int j=1;j<n;j++)//最小生成树有n-1条边
    {
        int minnode=GetMin();
        v[minnode]=1;//标记
        ans+=d[minnode];
        for(int i=1;i<=n;i++)
            d[i]=min(d[i],e[minnode][i]);//更新最短距离
    }
    return ans;
}

int main()
{
    long long i,x,y,z;
    memset(e,127/3,sizeof e);
    cin >> n >> m;
    for(i=1;i<=m;i++)
    {
        cin >> x >> y >> z;
        if(x==y)//防止自环
            continue;
        e[x][y]=e[y][x]=min(e[x][y],z);//防止重边
    }
    cout << prim();
    return 0;
}

Tips.题目有提示说要防止自环和重边

自环——边 e 的两个端点相同,则 e 称为一个自环。

重边——两个完全相同的边,称作(一组)重边。(无向图的边都是重边

出现重边的时候,取最短的

  • 13
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值