目录
题目
思路
并查集
代码(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;
}