村庄通电问题(最小生成树)Prim算法

问题描述

2015年,全中国实现了户户通电。作为一名电力建设者,小明正在帮助一带一路上的国家通电。
  这一次,小明要帮助 n 个村庄通电,其中 1 号村庄正好可以建立一个发电站,所发的电足够所有村庄使用。
  现在,这 n 个村庄之间都没有电线相连,小明主要要做的是架设电线连接这些村庄,使得所有村庄都直接或间接的与发电站相通。
  小明测量了所有村庄的位置(坐标)和高度,如果要连接两个村庄,小明需要花费两个村庄之间的坐标距离加上高度差的平方,形式化描述为坐标为 (x1 , y1) 高度为 h1 的村庄与坐标为 (x2 , y2) 高度为 h2 的村庄之间连接的费用为
  sqrt((x1 - x2)(x1 - x2)+(y1 - y2)(y1 - y2))+(h1 - h2)*(h1 - h2)。
  在上式中 sqrt 表示取括号内的平方根。请注意括号的位置,高度的计算方式与横纵坐标的计算方式不同。
  由于经费有限,请帮助小明计算他至少要花费多少费用才能使这 n 个村庄都通电。

输入格式

输入的第一行包含一个整数 n ,表示村庄的数量。
  接下来 n 行,每行三个整数 x, y, h,分别表示一个村庄的横、纵坐标和高度,其中只有第一个村庄可以建立发电站。

输出格式

输出一行,包含一个实数,四舍五入保留 2 位小数,表示答案。

样例输入

4
1 1 3
9 9 7
8 8 6
4 5 4

样例输出

17.41

评测用例规模与约定

对于 30% 的评测用例,1 <= n <= 10;
  对于 60% 的评测用例,1 <= n <= 100;
  对于所有评测用例,1 <= n <= 1000,0 <= x, y, h <= 10000。

大致题意

村子里有 n 个电线杆,分别给出他们的坐标(x, y) 和高度 h,问你如何架电线能够使得架电线费用少。注意给出的费用表达式。

求解思路

这道题给人第一印象就是一道最小生成树的题,即求使得所有点都连通的最小连通网。

本题用了典型的 Prim 算法。

开始时,所有的点之间都没有架起电线,我们先思考,对于第 i 个点,我们可以设置一个 vis [i] 属性,表示该点是否已经和其他点连接起来。一开始所有点的 vis 均为0(0即表示没有与其他点连接)。
这时我们可以任选一个点,假设我们选第一个点,将其 vis 置 1,然后遍历剩下的 n - 1 个点,找出其中距离第一个点最近的点,然后将其的 vis 置 1。
为方便叙述,我们这里先设 vis 为 1 的点的集合为 Q 集合。
之后我们再选第三个点,即 找出Q集合以外的一个点,它到Q集合中的一个点的值最小即可。 之后再将其加入Q集合。然后继续寻找下一个点直至结束。

找点的思路我们已经清晰;接下来就是要算值了。

我们这里设置了一个val数组,对于Q集合外的点,它存放的值即每个点到Q集合中的点的最小值,对于Q集合内的点,他的值即是最终它的架线费用。
则很好理解:我们在每一次将新的点加入Q集合当中时,都要去更新val数组的值。

代码

代码并不是很复杂,大家只需要深刻的理解prim部分。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<string>
using namespace std;
const int maxn=100;
const double MAX=0x7f7f7f7f;
double val[maxn];
double a[maxn][maxn];
bool vis[maxn];
int n;
struct point
{
    int x,y,h;
}p[maxn];
void getvalue(){          //将两点之间的架电线费用存入邻接矩阵a[i][j]中;
    int i,j;
    for(i=1;i<=n;i++){
        for(j=1;j<=n;j++){
            if(i==j) a[i][j]=MAX;
            else
            a[i][j]=a[j][i]=sqrt( (p[i].x - p[j].x) * (p[i].x - p[j].x) + (p[i].y-p[j].y) * (p[i].y-p[j].y)) + (p[i].h-p[j].h) * (p[i].h-p[j].h);
        }
    }
}
void prim(){      //prim算法
    int cnt=0;       //记录架线的条数
    vis[1]=1;
    val[0]=MAX; val[1]=0;       //这里将val[0]设为MAX,是为了和后面代码的t=0相对应,便于写循环,稍加理解即可。
    for(int i=2;i<=n;i++)       //将所有点到 Q 集合中的路径存入相应的val[i],此时Q集合中只有第一个点,则只需计算其他点到第一个点的距离
        val[i]=a[1][i];
    //以上为初始化的工作
    while(cnt<n){          //终止条件为架线条数为n-1,此时为最小生成树。
        int t=0,vmin=MAX;
        for(int j=1;j<=n;j++)     //找出val数组中的最小值,即 将要加入的点的值。
            if(vis[j]==0&&val[j]<val[t])
                t=j;
        vis[t]=1;  //加入Q集合
        cnt++;    //架线条数加1
        for(int i=1;i<=n;i++){        //更新Q集合外的每个点到Q集合中的点的最小值
            if(vis[i]==0&&a[t][i]<val[i]){
                val[i]=a[t][i];
            }
        }
    }
}
int main(){
    cin>>n;
    for(int i = 1; i <= n; i++)
    scanf("%d %d %d", &p[i].x, &p[i].y, &p[i].h);
    getvalue();
    memset(vis,0,sizeof(vis));
    prim();
    double ans=0;
    for(int i=1;i<=n;i++){     //此时val存放的即是每条线的费用值
        ans+=val[i];
    }
    printf("%.2f", ans);     //注意输出两位小数
    return 0;
}

如有任何问题欢迎大家留言,还请大家批评指正。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

茂爱学习

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

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

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

打赏作者

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

抵扣说明:

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

余额充值