新的开始-

新的开始


题目描述

image-20210725122343693


核心思路

这个题目有点特殊,我们把每个矿井都看作图中的一个节点,由于在矿井iii上建立一个发电站,需要费用viv_ivi,也就是说节点上是有权值的。然后题目想要问的是连通所有节点所需要的最小花费,这很明显其实就是想要求最小生成树。

题目中的第一个方法表明了每个节点上是可以有权值的;第二个方法表明了节点之间的边是有权值的。如果只有边权,那么就是我们熟悉的模型,直接求解这个图的最小生成树即可。但是特别之处在于节点也是有权值的,因此我们就不能对这张图直接求解最小生成树了。需要做如下转化:

我们可以建立一个虚拟源点(超级发电站),如果某个矿井iii上有权值viv_ivi,那么我们就转化为虚拟源点到这个节点之间的边权为viv_ivi,也就是说我们巧妙地把节点的权值转移到了虚拟源点与该节点之间的边的权值,而且是等效的。那么,我们通过这么做之后,就会得到一张n+1n+1n+1个节点的新图,而且这个新图只有边是有权值的,顶点不再带有权值,于是就转化为了我们熟悉的模型,那么就可以对这张新图直接求解最小生成树了。

如下图所示:

image-20210725123841634

举个栗子:

拿题目样例解释:

image-20210725124421133


代码

写法1:prim算法

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=310;
int n;
int g[N][N];
int dist[N];
bool st[N];
//prim算法求解最小生成树
int prim()
{
    int res=0;  //让所有矿井获得充足电能的最小花费
    //初始化每个节点到S集合的距离为正无穷
    memset(dist,0x3f,sizeof dist);
    //初始化虚拟源点到达S集合的距离为0
    dist[0]=0;
    //由于最终形成的最小生成树中有n+1个节点
    //每跑一趟循环就能把一个节点加入S集合,有n+1个节点,因此需要跑n+1次循环
    for(int i=0;i<n+1;i++)
    {
        //t记录的是此时距离S集合最近的点
        int  t=-1;
        for(int j=0;j<=n;j++)
            if(!st[j]&&(t==-1||dist[t]>dist[j]))
                t=j;
        //标记点t已经被加入了S集合
        st[t]=true;
        //累加最小生成树的权值
        res+=dist[t];
        //枚举节点t的所有邻接点  更新这些点到集合S的距离
        for(int k=0;k<=n;k++)
            dist[k]=min(dist[k],g[t][k]);
    }
    return res;
}
int main()
{
    scanf("%d",&n);
    //将矿井节点上的权值vi转化为虚拟源点与该节点之间的边权
    for(int i=1;i<=n;i++)
    {
        int w;
        scanf("%d",&w);
        //最小生成树无向图  
        g[0][i]=g[i][0]=w;
    }
    //输入矿井i和矿井j之间建立电网的费用
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            scanf("%d",&g[i][j]);
    printf("%d\n",prim());
    return 0;
}

写法2:Kruskal算法

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=310,M=N*N;
int n,cnt;      //cnt是总共的边数
struct Edge{
    int a,b,w;
    bool operator < (const Edge& W)const{
        return w<W.w;
    }
}edges[M];
int p[N];
int find(int x)
{
    if(x!=p[x])
        p[x]=find(p[x]);
    return p[x];
}
int Kruskal()
{
    int res=0;
    sort(edges,edges+cnt);
    for(int i=0;i<=n;i++)
        p[i]=i;
    for(int i=0;i<cnt;i++)
    {
        int a=find(edges[i].a);
        int b=find(edges[i].b);
        int w=edges[i].w;
        if(a!=b)
        {
            p[a]=b;
            res+=w;
        }
    }
    return res;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int w;
        scanf("%d",&w);
        edges[cnt++]={0,i,w};
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            int w;
            scanf("%d",&w);
            edges[cnt++]={i,j,w};
        }
    }
    printf("%d\n",Kruskal());
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

计小酱蟹不肉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值