【AcWing】蓝桥杯集训每日一题Day15|并查集|528.奶酪(C++)

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)=(x1x2)2+(y1y2)2+(z1z2)

输入格式

每个输入文件包含多组数据。  
输入文件的第一行,包含一个正整数 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个数据,

可以看作一个图

可以把每个球看作是图论中的一个点,如果两个球,它们是相交或相切的,就可以连条边,整个上边界可以看成一个点,整个下边界可以看成是一个点,

  1. 如果一个球和上边界是连通的,就从这个球到上边界的这个点连一条边
  2. 如果一个球和下边界是连通的,就从这个球的点到下边界连一条边
    下边界的点可以标记为S,上边界的点可以标记为T。
    边都是相互的,是一个无向图
方法

给一个无向图,问S和T是不是连通的
方法有很多

  1. 图的遍历
    1. BFS
    2. DFs
  2. 并查集
    1. 两个点如果是连通的,就把这两个点所在的集合合并,合并完之后,只要看一下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 20106,2000万的计算量,是可以过的

如何判断两个球是不是连通的

在空间中求一下两个球心的距离
如果两个球心的距离刚好是两倍的半径的话,就是相切
如果大于2r,就是相离
如果小于2r,就是相交

求距离的时候可以直接根据数学公式来写,这样写会涉及到浮点数,浮点数比较大小的时候,C++里边,需要处理一下精度问题,
如何判断A小于等于B:A < B + eps,这个eps是可以调的,一般是 1 0 − 8 10^{-8} 108
如何判断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~n
		for (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};

			//判断一下球什么时候和上下边界的距离小于等于r
			if (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;
}
  • 19
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值