528.奶酪
528. 奶酪 - AcWing题库 |
---|
难度:简单 |
时/空限制:1s / 128MB |
总通过数:3800 |
总尝试数:10480 |
来源: NOIP2017提高组 |
算法标签 并查集BFSDFS |
题目内容
现有一块大奶酪,它的高度为 ℎ,它的长度和宽度我们可以认为是无限大的,奶酪中间有许多半径相同的球形空洞。
我们可以在这块奶酪中建立空间坐标系,在坐标系中,奶酪的下表面为 z=0,奶酪的上表面为 z=h。
现在,奶酪的下表面有一只小老鼠 Jerry,它知道奶酪中所有空洞的球心所在的坐标。
如果两个空洞相切或是相交,则 Jerry 可以从其中一个空洞跑到另一个空洞
特别地,如果一个空洞与下表面相切或是相交,Jerry 则可以从奶酪下表面跑进空洞;如果一个空洞与上表面相切或是相交,Jerry 则可以从空洞跑到奶酪上表面。
位于奶酪下表面的 Jerry 想知道,在不破坏奶酪的情况下,能否利用已有的空洞跑到奶酪的上表面去?
空间内两点 P1(x1,y1,z1)、P2(x2,y2,z2) 的距离公式如下:
d i s t ( P 1 , P 2 ) = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 + ( z 1 − z 2 ) dist(P_{1},P_{2})=\sqrt{ (x_{1}-x_{2})^2+(y_{1}-y_{2})^2+(z_{1}-z_{2}) } dist(P1,P2)=(x1−x2)2+(y1−y2)2+(z1−z2)
输入格式
每个输入文件包含多组数据。
输入文件的第一行,包含一个正整数 T,代表该输入文件中所含的数据组数。
接下来是 T 组数据,每组数据的格式如下:
第一行包含三个正整数 n,ℎ 和 r,两个数之间以一个空格分开,分别代表奶酪中空洞的数量,奶酪的高度和空洞的半径。
接下来的 n 行,每行包含三个整数 x、y、z,两个数之间以一个空格分开,表示空洞球心坐标为 (x,y,z)。
输出格式
输出文件包含 T 行,分别对应 T 组数据的答案,如果在第 i 组数据中,Jerry 能从下表面跑到上表面,则输出 Yes
,如果不能,则输出 No
。
数据范围
1≤n≤1000,
1≤h,r≤10^9,
T≤20,
坐标的绝对值不超过10^9
输入样例:
3
2 4 1
0 0 1
0 0 3
2 5 1
0 0 1
0 0 4
2 5 2
0 0 2
2 0 4
输出样例:
Yes
No
Yes
题目解析
有一块大奶酪,是一个三维问题,有上边界和下边界,左右两边是无限长的,下边界的坐标是z=0,上边界的坐标是z=h
其中有很多的洞,所有的球的半径都是固定的,是r
有可能给到上边界上边,这时认为这个洞不存在
如果和边界相交,可以认为这个洞只有一半
只要两个球相交或者相切,就可以认为这两个球的空间是连通的,这个老鼠就可以互穿,如果这个球和上边界相切的话,就认为老鼠和上面的区域是连通的,如果洞和下边界相切,就可以认为老鼠可以通过这个洞和下面的区域连通
问上边界和下边界整个是不是连通的,从下边界能不能通过一些洞到上边界,可以输出YES,否则输出NO
最多一共有1000个球,最多包含20个数据,
可以看作一个图
可以把每个球看作是图论中的一个点,如果两个球,它们是相交或相切的,就可以连条边,整个上边界可以看成一个点,整个下边界可以看成是一个点,
- 如果一个球和上边界是连通的,就从这个球到上边界的这个点连一条边
- 如果一个球和下边界是连通的,就从这个球的点到下边界连一条边
下边界的点可以标记为S,上边界的点可以标记为T。
边都是相互的,是一个无向图
方法
给一个无向图,问S和T是不是连通的
方法有很多
- 图的遍历
- BFS
- DFs
- 并查集
- 两个点如果是连通的,就把这两个点所在的集合合并,合并完之后,只要看一下S和T是不是在一个集合里,就可以了
如何建边
N很小,只有1000
所以可以直接暴力枚举每两个球是不是连通的,N^2枚举一遍
1000个点最多100万条边
不管是并查集还是图的遍历,时间复杂度都是O(N+M),所以时间复杂度都是 1 0 6 10^6 106级别
一共有20个数据,每一个测试数据包含20个测试点,所以每个测试数据,整个的运行时间是 20 ∗ 1 0 6 20*10^6 20∗106,2000万的计算量,是可以过的
如何判断两个球是不是连通的
在空间中求一下两个球心的距离
如果两个球心的距离刚好是两倍的半径的话,就是相切
如果大于2r,就是相离
如果小于2r,就是相交
求距离的时候可以直接根据数学公式来写,这样写会涉及到浮点数,浮点数比较大小的时候,C++里边,需要处理一下精度问题,
如何判断A小于等于B:A < B + eps,这个eps是可以调的,一般是 1 0 − 8 10^{-8} 10−8
如何判断A小于B:A < B - eps
如何判断A等于B:|A-B| < eps
如果不想处理精度问题,可以用整数来做,把等式两边平方一下,就变成和 4 r 2 4r^2 4r2比较
代码
#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;typedef long long LL;const int N = 1010;//定义一个结构体来存储球
int n, h, r;
struct Sphere
{//球心坐标int x, y, z;
}q[N];//p是并查集代表元素数组
int p[N];//并查集模板
int find(int x)
{if (p[x] != x) p[x] = find(p[x]);return p[x];
}int main()
{//定义一下测试数据的数量int T;scanf("%d", &T);while (T --){//读入一个点数scanf("%d%d%d", &n, &h, &r);//初始化并查集,下边界的点是0,上边界的点是n+1,中间点的编号是1~nfor (int i = 0; i <= n + 1; i ++) p[i] = i;//依次读入每个点for (int i = 1; i <= n; i ++){int x, y, z;scanf("%d%d%d", &x, &y, &z);//把每个点的坐标存下来q[i] = {x, y, z};//判断一下球什么时候和上下边界的距离小于等于rif (abs(z) <= r) p[find(i)] = find(0);if (abs(z - h) <= r) p[find(i)] = find(n + 1);}//枚举一下所有中间的球for (int i = 1; i <= n; i ++)for (int j = 1; j < i; j ++){//求出坐标的差值LL dx = q[i].x - q[j].x;LL dy = q[i].y - q[j].y;LL dz = q[i].z - q[j].z;if (dx * dx + dy * dy + dz * dz <= 4 * (LL)r * (LL)r)p[find(i)] = find(j);}if (find(0) == find(n + 1)) puts("Yes");else puts("No");}return 0;
}