这显然是一道并查集(如果没有学过并查集的童鞋还是去找找别的博客吧)
思路比较简单,就是如果两个洞相交(或相切),就把它们连入一个集合,可以想象一个集合就是一条通道
我们只需要判断每一条通道是否存在元素与底部、顶部相连即可。如果都有,那么输出Yes
那么问题来了,如何判断两个球是否相交(切)呢?
其实如果你数学很好、做题经验丰富 ,你就会知道了:
如果两个球的半径之和>=两个球球心的距离,那么两圆相交(切)。(emm这应该很容易想到)
具体操作看注释
//注释&代码 @copyright lzoi_hmh
//并查集
#include <iostream>
#include <cstdio>
#include <cmath>
#define MAXN 1001
#define ll long long
using namespace std;
//fa[i]为并查集模板操作,表示第i个洞的祖先
//tot_top表示通到顶上的洞的个数,tot_bottom表示通到底部的洞的个数
//top[i]表示第i个通到顶上的洞,bottom[i]表示第i个通到底部的洞
int fa[MAXN],t,n,top[MAXN],bottom[MAXN],tot_top,tot_bottom;
ll x[MAXN],y[MAXN],z[MAXN],r,h;
int find(int x) //找祖先
{
if (x!=fa[x]) fa[x]=find(fa[x]);
return fa[x];
}
double cnt_dis(ll x,ll y, ll z,ll x1, ll y1,ll z1) //计算两圆心之间的距离
{
double ans;
ans=sqrt((x-x1)*(x-x1) + (y-y1)*(y-y1) + (z-z1)*(z-z1));
return ans;
}
int main()
{
scanf("%d",&t);
for (int i=1;i<=t;i++){
scanf("%d %lld %lld",&n,&h,&r);
tot_top=0; tot_bottom=0;
for (int j=1;j<=n;j++){ //并查集初始操作
fa[j]=j;
}
for (int j=1;j<=n;j++){
scanf("%lld %lld %lld",&x[j],&y[j],&z[j]);
if (z[j]+r>=h){ //找所有通到顶部的洞
tot_top++;
top[tot_top]=j;
}
if (z[j]-r<=0){ //找所有通到底部的洞
tot_bottom++;
bottom[tot_bottom]=j;
}
for (int k=1;k<j;k++){ //连祖先
if (cnt_dis(x[k],y[k],z[k],x[j],y[j],z[j])<=2*r){
int fa1=find(k);
int fa2=find(j);
if (fa1!=fa2){
fa[fa1]=fa2;
}
}
}
}
int s=0;
for (int j=1;j<=tot_top;j++){
for (int k=1;k<=tot_bottom;k++){
if (find(top[j])==find(bottom[k])){ //如果一个底部的洞和一个顶上的洞祖先是一样的,就说明有一条通路
s=1;
break;
}
}
if (s==1) break;
}
if (s==1) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}