题解 | #[NOIP2017]奶酪#
[NOIP2017]奶酪
https://ac.nowcoder.com/acm/problem/16417
链接:https://ac.nowcoder.com/acm/problem/16417
来源:牛客网
思路:先分别存储底部和顶部的点的下标,然后因为n的范围较小,所以可以用双重循环求解
来源:牛客网
题目描述
现有一块大奶酪,它的高度为 h,它的长度和宽度我们可以认为是无限大的,奶酪中间有许多半径相同的球形空洞。我们可以在这块奶酪中建立空间坐标系, 在坐标系中,奶酪的下表面为 z = 0,奶酪的上表面为 z = h。
现在, 奶酪的下表面有一只小老鼠 Jerry, 它知道奶酪中所有空洞的球心所在的坐标。如果两个空洞相切或是相交,则 Jerry 可以从其中一个空洞跑到另一个空洞,特别地,如果一个空洞与下表面相切或是相交, Jerry 则可以从奶酪下表面跑进空洞; 如果一个空洞与上表面相切或是相交, Jerry 则可以从空洞跑到奶酪上表面。
位于奶酪下表面的 Jerry 想知道, 在不破坏奶酪的情况下,能否利用已有的空洞跑到奶酪的上表面去?
空间内两点 P1(x1,y1,z1) 、P2(x2,y2,z2) 的距离公式如下:
dist(P1,P2)=(x1−x2)2+(y1−y2)2+(z1−z2)2dist(P_1,P_2)=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2}dist(P1,P2)=(x1−x2)2+(y1−y2)2+(z1−z2)2
现在, 奶酪的下表面有一只小老鼠 Jerry, 它知道奶酪中所有空洞的球心所在的坐标。如果两个空洞相切或是相交,则 Jerry 可以从其中一个空洞跑到另一个空洞,特别地,如果一个空洞与下表面相切或是相交, Jerry 则可以从奶酪下表面跑进空洞; 如果一个空洞与上表面相切或是相交, Jerry 则可以从空洞跑到奶酪上表面。
位于奶酪下表面的 Jerry 想知道, 在不破坏奶酪的情况下,能否利用已有的空洞跑到奶酪的上表面去?
空间内两点 P1(x1,y1,z1) 、P2(x2,y2,z2) 的距离公式如下:
dist(P1,P2)=(x1−x2)2+(y1−y2)2+(z1−z2)2dist(P_1,P_2)=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2}dist(P1,P2)=(x1−x2)2+(y1−y2)2+(z1−z2)2
输入描述:
每个输入文件包含多组数据。 输入文件的第一行,包含一个正整数 T,代表该输入文件中所含的数据组数。 接下来是 T 组数据,每组数据的格式如下: 第一行包含三个正整数 n, h 和 r, 两个数之间以一个空格分开,分别代表奶酪中空洞的数量,奶酪的高度和空洞的半径。 接下来的 n 行,每行包含三个整数 x, y, z, 两个数之间以一个空格分开, 表示空洞球心坐标为 (x,y,z)。
输出描述:
输出文件包含 T 行,分别对应 T 组数据的答案,如果在第 i 组数据中, Jerry 能从下表面跑到上表面,则输出“Yes”,如果不能,则输出“No”(均不包含引号)。
合并两个集合的条件:两个球心之间的距离小于等于半径的2倍(即相互接触)
题目难度一般,一般都能想到用并查集求解,但是需要将判断是否连通的条件转化为判断某个与顶部相连的点和某个与底部相连的点是否处于同一集合中,然后想到分别存储底部和顶部的点的下标,再想到如何正确的合并两个集合。
代码如下:
#include<stdio.h>
#include<math.h>
#define ll long long int
int father[1020],di[1020],ding[1020]; //father用来存储父节点,di用来存储与底面接触的点,ding用来存储与顶面接触的点
struct ss{
ll x,y,z;
}d[1020];
int find(int x){ //查找函数
if(father[x]==x) return x;
else return father[x]=find(father[x]);
}
double dis(ll x1,ll y1,ll z1,ll x2,ll y2,ll z2){ //计算球心间的距离
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)+(z1-z2)*(z1-z2));
}
void merge(int x,int y){ //合并函数
if(find(x)!=find(y)) father[find(x)]=find(y);
}
int main(){
int T,n,a=1,b=1;
ll h,r;
scanf("%d",&T);
while(T--){
a=1;b=1; //重中之重,不加这一句直接WA
scanf("%d %lld %lld",&n,&h,&r);
for(int i=1;i<=n;i++) father[i]=i;
for(int i=1;i<=n;i++){
scanf("%lld %lld %lld",&d[i].x,&d[i].y,&d[i].z);
if(d[i].z<=r) di[a++]=i; //记录与地面相连的点的上标
if(d[i].z+r>=h) ding[b++]=i; //记录与顶面相连的点的下标
for(int j=1;j<i;j++){
if(dis(d[i].x,d[i].y,d[i].z,d[j].x,d[j].y,d[j].z)<=(2.0*r)) merge(i,j); //如果两个球心之间的距离小于等于半径的2倍的话,就合并i和j
}
}
int flag=0;
for(ll i=1;i<a;i++){
for(ll j=1;j<b;j++){
if(find(di[i])==find(ding[j])){ //如果存在接触顶面的点与接触底面的点处于同一个集合中,那么可以确定顶部与底部至少有一条连通路
flag=1;
break;
}
}
if(flag==1) break; //减少循环次数
}
if(flag==0) printf("No\n");
else printf("Yes\n");
}
return 0;
}
#include<math.h>
#define ll long long int
int father[1020],di[1020],ding[1020]; //father用来存储父节点,di用来存储与底面接触的点,ding用来存储与顶面接触的点
struct ss{
ll x,y,z;
}d[1020];
int find(int x){ //查找函数
if(father[x]==x) return x;
else return father[x]=find(father[x]);
}
double dis(ll x1,ll y1,ll z1,ll x2,ll y2,ll z2){ //计算球心间的距离
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)+(z1-z2)*(z1-z2));
}
void merge(int x,int y){ //合并函数
if(find(x)!=find(y)) father[find(x)]=find(y);
}
int main(){
int T,n,a=1,b=1;
ll h,r;
scanf("%d",&T);
while(T--){
a=1;b=1; //重中之重,不加这一句直接WA
scanf("%d %lld %lld",&n,&h,&r);
for(int i=1;i<=n;i++) father[i]=i;
for(int i=1;i<=n;i++){
scanf("%lld %lld %lld",&d[i].x,&d[i].y,&d[i].z);
if(d[i].z<=r) di[a++]=i; //记录与地面相连的点的上标
if(d[i].z+r>=h) ding[b++]=i; //记录与顶面相连的点的下标
for(int j=1;j<i;j++){
if(dis(d[i].x,d[i].y,d[i].z,d[j].x,d[j].y,d[j].z)<=(2.0*r)) merge(i,j); //如果两个球心之间的距离小于等于半径的2倍的话,就合并i和j
}
}
int flag=0;
for(ll i=1;i<a;i++){
for(ll j=1;j<b;j++){
if(find(di[i])==find(ding[j])){ //如果存在接触顶面的点与接触底面的点处于同一个集合中,那么可以确定顶部与底部至少有一条连通路
flag=1;
break;
}
}
if(flag==1) break; //减少循环次数
}
if(flag==0) printf("No\n");
else printf("Yes\n");
}
return 0;
}
算法入门班习题题解&感悟 文章被收录于专栏
这是本人对于算法入门班共十三章习题的题解与个人的一些感悟,本人写题解的目的是对于某些经典的/陌生的/无从下手的/思维方向错误的题目加深印象。 希望自己能够在学习算法的路上走得更远,今天也是努力学习的一天!