奶酪——并查集,BFS,DFS(NOIP2017提高组)

目录

题目

思路

并查集

代码(java)

BFS(DFS同理)

代码(C++)


题目

思路

        这个题目意思是有很多个球分布在一个三维空间内,如果这些球相切或者相交都可以互相到达,我们需要判断能否通过这些球从底部到达顶部。可以抽象为,所有的球构成了一个图,球即是图中的点,相切或者相交说明两点之间有线。因此我们可以想到,将所有的点组成一个集合,只需要判断这个集合能否到达底部和顶部。

        因此可以使用并查集来组成集合。

        最主要的是这个题n只有1000,因此暴力做也没问题。

并查集

        我们判断每两个球,是否相交或者相切,将所有相切或者相交的球装入到一个集合中去,最后判断是否到达高度为0和n。

        构成集合后,如何判断是否到达 0 和 h?我们可以事先在读入球心数据的时候,判断该球是否与 z = 0 相切或者相交,同样判断是否与 z = h 相切或者相交。

        我这里用 0 表示与第 0 层相交,n+1 表示与第 h 层相交。(因为使用数组下标来表示该球,因此用 n+1 来表示n个球之外的,表示到达h)。

        如果与 0 相交,则 find(i) = 0;与h相交,则 find(i)=n+1;

        最后如果 find(0) == find(n+1) ,则说明可以到达。

        因为需要遍历每两个球,所以时间复杂度是O(n^2)

代码(java)

        注意只有一个球的时候 

import java.io.*;
import java.util.*;

class Main{
    static int N = 1010;
    static int T;
    static int n,h,r;
    static int[] p = new int[N];
    static int[] x = new int[N];
    static int[] y = new int[N];
    static int[] z = new int[N];
    static boolean[] st = new boolean[N];
    public static void main(String[] args) throws IOException{
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        T = Integer.parseInt(in.readLine().split(" ")[0]);
        while(T-->0){
            String[] s = in.readLine().split(" ");
            n = Integer.parseInt(s[0]);
            h = Integer.parseInt(s[1]);
            r = Integer.parseInt(s[2]);
            // 初始化p[]
            for(int i=0;i<=n+1;i++)
                p[i] = i;
            // 读取
            for(int i=1;i<=n;i++){
                s = in.readLine().split(" ");
                x[i] = Integer.parseInt(s[0]);
                y[i] = Integer.parseInt(s[1]);
                z[i] = Integer.parseInt(s[2]);
                if(z[i]+r>=h) p[i] = find(n+1); // 如果连接顶部,将他加到n+1集合
                if(z[i]-r<=0) { // 如果连接底部,其中也同样连接顶部,则直接令find(0)==find(n+1),否则,加到0集合
                    if(p[i]!=i) p[find(0)] = find(n+1);
                    else p[i] = find(0);
                }
            }
            // 构成并查集
            for(int i=1;i<=n;i++){
                for(int j=1;j<=n;j++){
                    if(get(x[i],x[j],y[i],y[j],z[i],z[j])<=4.0*r*r){
                        int pi = find(i);
                        int pj = find(j);
                        if(pi!=pj){
                            p[pi] = pj;
                        }
                    }
                }
            }
            if(find(0)==find(n+1)) System.out.println("Yes");
            else System.out.println("No");
        }
    }
    // 找根节点
    public static int find(int u){
        if(p[u]!=u) p[u] = find(p[u]);
        return p[u];
    }
    // 计算两个圆的路径长度
    public static double get(int x1,int x2,int y1,int y2,int z1,int z2){
         return Math.pow(x1-x2,2)+Math.pow(y1-y2,2)+Math.pow(z1-z2,2);
    }
}

BFS(DFS同理)

        我们可以不事先将所有的球都装入到一个集合,我们直接从最底部的球进行判断,使用bfs向上进行扩展,如果扩展到某个球到达h,说明可以到达。

        相当于是一个多源bfs问题,因为与0相交或者相切的球可能有多个。

        每个球都只需要遍历一遍,但遍历到每个球的时候都需要遍历一遍所有的球,找出其中与当前球相切或者相交的球,然后进行bfs。

        因为每遍历到一个球都需要遍历所有的球,因此时间复杂度也是O(n^2),但是比并查集快,因为如果中途遍历到可以到达 h,则不用再遍历,而并查集要遍历所有的点。

代码(C++)

作者:Conan15
链接:https://www.acwing.com/solution/content/99009/ 

#include <bits/stdc++.h>
using namespace std;
int t, n, h, r, f[1010];
double x[1010], y[1010], z[1010];
inline double dist(double X1, double X2, double Y1, double Y2, double Z1, double Z2) {
    return sqrt((X1 - X2) * (X1 - X2) + (Y1 - Y2) * (Y1 - Y2) + (Z1 - Z2) * (Z1 - Z2));
}

int main() {
    scanf("%d", &t);
    while (t--) {
        scanf("%d%d%d", &n, &h, &r);
        for (int i = 1; i <= n; i++) scanf("%lf%lf%lf", &x[i], &y[i], &z[i]), f[i] = 0;
        queue<int> q; bool ok = 0;
        for (int i = 1; i <= n; i++) 
            if (z[i] <= r) q.push(i), f[i] = 1;
        while (q.size()) {
            int p = q.front(); q.pop();
            if (ok) break;
            if (z[p] + r >= h) {ok = 1; puts("Yes"); break;}
            for (int i = 1; i <= n; i++) {
                if (f[i]) continue;
                if (dist(x[p], x[i], y[p], y[i], z[p], z[i]) <= 2 * r) {
                    q.push(i), f[i] = 1;
                    if (z[i] + r >= h) {ok = 1; puts("Yes"); break;}
                }
            }
        }
        if (!ok) puts("No");
    }
    return 0;
}
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值