蓝桥杯:通电

蓝桥杯: 通电https://www.lanqiao.cn/problems/162/learning/

目录

题目描述

输入描述

输出描述

输入输出样例

输入

输出

题目分析(最小生成树):

 AC代码(Java)


题目描述

        2015 年,全中国实现了户户通电。作为一名电力建设者,小明正在帮助一带一路上的国家通电。

        这一次,小明要帮助 n 个村庄通电,其中 1 号村庄正好可以建立一个发电站,所发的电足够所有村庄使用。

        现在,这 n 个村庄之间都没有电线相连,小明主要要做的是架设电线连接这些村庄,使得所有村庄都直接或间接的与发电站相通。

        小明测量了所有村庄的位置(坐标)和高度,如果要连接两个村庄,小明需要花费两个村庄之间的坐标距离加上高度差的平方,形式化描述为坐标为(x1​,y1​) 高度为 ℎ1​ 的村庄与坐标为 (x2​,y2​) 高度为 ℎ2 的村庄之间连接的费用为

        高度的计算方式与横纵坐标的计算方式不同。

        由于经费有限,请帮助小明计算他至少要花费多少费用才能使这 n 个村庄都通电。

输入描述

        输入的第一行包含一个整数 n ,表示村庄的数量。

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

        其中,1≤n≤1000,0≤x,y,h≤10000。

输出描述

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

输入输出样例

        输入

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

         输出

17.41

题目分析(最小生成树):

        把村庄看成顶点,求每个村庄之间搭建电线的花费最少,也就是每个顶点直接相连所需要的花费最少,很明显是考最小生成树。

        如果不了解最小生成树,可以先看:蓝桥杯:聪明的猴子。这个题是最小生成树的模板题。

        题目有个附加条件,就是1号村庄是有发电站的,并且其他村庄想要有电,要么直接从1号村庄建立电线,要么和已经与1号村庄建立电线的村庄来建立电线,如:

        假如2号村庄已经同1号村庄架设了电线,那么此时1号村庄和2号村庄都是通电的,也就是说,3号村庄与1号村庄、2号村庄搭设电线,他都能通电,因此,我们只需要根据最小生成树的贪心策略(每次选择花费最小的一条边进行连接),即可完成所有村庄的通电。

        因为最小生成树是用最小花费建立一个连接所有顶点的图,那么,也就是1号村庄是肯定会被连接的,我们不需要对1号村庄进行特殊处理,直接套用最小生成树的模板即可(因此本题也是一个最小生成树的模板题)。

        PS:注意,建立边的花费是浮点数

 AC代码(Java)

        在写优先队列的升序排序的时候,记得题目中的代价(也可以叫做建立边的花费,这里用price来表示)是浮点数的,所以要用对应的包装类的比较方法compare(),我当成整型来处理,写成了(int)e1.price-e2.price,最后一个用例没过。

        应该是用Double的compare方法来比较两个price。

import java.util.*;
// 1:无需package
// 2: 类名必须Main, 不可修改

public class Main {
    //并查集模板
    static int[] pre;
    public static void initPre(int n){
        pre = new int[n+5];
        for(int i = 1;i<=n;i++)
            pre[i] = i;
    }
    public static int find(int x) {
        if(pre[x] == x) return x;
        return pre[x] = find(pre[x]);
    }
    public static void join(int a,int b) {
        pre[find(a)] = pre[find(b)];
    }

    //存储村庄的坐标信息
    static class Point{
        int id; //村庄编号,唯一标识
        int x; //x轴
        int y; //y轴
        int h; //高
        public Point(int id,int x,int y,int h) {
            this.id = id;
            this.x = x;
            this.y = y;
            this.h = h;
        }
    }
    //两个村庄之间建立边
    static class Edge{
        //self和target建立边(架设电线)
        Point self;
        Point target;
        double price; //建立边的花费
        public Edge(Point self,Point target,double price) {
            this.self = self;
            this.target = target;
            this.price = price;
        }
    }
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);

        int n = scan.nextInt(); //村庄编号
        initPre(n);
        //建立一个列表存放村庄信息
        List<Point> list = new ArrayList<>();
        for(int i = 1;i<=n;i++){
            int x = scan.nextInt();
            int y = scan.nextInt();
            int h = scan.nextInt();
            list.add(new Point(i,x,y,h));
        }
        scan.close();

        //将所有村庄之间架设电线(建边)的信息存放到优先队列之中,按照升序排序
        //(PS:这里使用Double的compare来比较大小,如果直接用(int)e1.price-e2.price,将比较结果转换成(int),最后一个用例有错),Debug了一个小时.....
        PriorityQueue<Edge> pq = new PriorityQueue<>((e1,e2)->((Double.compare(e1.price,e2.price))));
        for(int i = 1;i<n;i++){
            //编号为i的村庄存放在列表的i-1处
            Point self = list.get(i-1);
            for(int j = i+1;j<=n;j++){
                Point target = list.get(j-1);
                //计算他们之间的花费
                double x = Math.pow(self.x-target.x,2); //(x1-x2)^2
                double y = Math.pow(self.y-target.y,2); //(y1-y2)^2
                double h = Math.pow(self.h-target.h,2); //(h1-h2)^2
                double price = Math.sqrt(x+y) + h; //对x+y开根号 + h
                //放入优先队列之中
                pq.add(new Edge(self,target,price));
            }
        }
        //收集完全部边的信息之后,每次取出花费最小的一条边建立边
        int e = 0; //对于一个顶点为n的图,最短路径应该是n-1条边
        double ans = 0;
        while(!pq.isEmpty()) {
            Edge edge = pq.poll();
            Point self = edge.self;
            Point target = edge.target;
            //判断建立这条边是否会产生环,如果会,就跳过
            if(find(self.id) == find(target.id)) {
                continue;
            }
            //如果不会产生环,那么就建立这条边,同时记录花费
            join(self.id,target.id);
            ans += edge.price;
            e++;
            if(e == n-1) break;
        }
        //格式化输出,保留两位小数
        System.out.printf("%.2f",ans);
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值