P3958 [NOIP2017 提高组] 奶酪 简单题解 (并查集)

来写一篇题解。

关于并查集的基础内容请点击这里

题目传送门 洛谷P3598

思路

首先,我们可以将这些洞想象成一个点(就是球心)。题目询问的是小老鼠能否从奶酪最底下跑到奶酪最上面。意思就是,是否存在一个通道(连续很多个洞)从底面通向顶层。

这时候,就会自然而然的想到从底面的点开始搜索,能搜索到上面的点就输出Yes。(当然,搜索也是可以通过这一题的)

但我们应该思考:还有没有其他的方法?
当然有了不然就不会有下面的文字了

我们可以换一个角度想,对每一个能互相到达的点,我们都将它们染上一个颜色,最后只需要寻找顶部与底部有没有染成相同颜色的点就行了。

但我们肯定不会真的去“染色”,因为染色问题一般都用dfs(根据我现在的知识),用了染色,不就成了搜索了嘛。将思路转化一下,实际上,我们只需要对这些能互相联通的点打上相同的标记即可。

你想到了什么?自然而然的想到并查集啦。并查集对于能互相联通的点都会有一个相同的标记。这个时候问题就变成了求互联通的点的并查集,再对顶面和底面的点遍历,检查他们的祖先是否相同。

但是,这些点能否联通用什么来判断呢?
题目给了空间坐标系中的两点距离公式

d i s t ( P 1 , P 2 ) = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 + ( z 1 − z 2 ) 2 dist(P_1,P_2)=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2} dist(P1,P2)=(x1x2)2+(y1y2)2+(z1z2)2

实际上,我们只需判断这两个点的距离是否小于半径的两倍就可以啦。
当然,为了保证精度,我们只需比较它们的平方即可。即
d i s t ( P 1 , P 2 ) 2 = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 + ( z 1 − z 2 ) 2 dist (P_1,P_2)^2=(x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2 dist(P1,P2)2=(x1x2)2+(y1y2)2+(z1z2)2

代码

下面上代码(有注释)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstring>
#include<vector>
#define ll long long
#define uint unsighed int 
#define ull unsighed long long
using namespace std;
const ll N=5e5+10;
const ll Z=1e9+7;

ll f[N]; 
ll f1[N],f2[N],cnt1,cnt2; //f1记录在顶面的点,f2记录在底面的点
ll x[N],y[N],z[N]; //存坐标


void init(int n) //初始化
{
	cnt1=cnt2=0;
	for(int i=1;i<=n;++i) f[i]=i;
}
	
int find(int x) //并查集与路径压缩
{
	if(x==f[x]) return x;
	return f[x]=find(f[x]);
}

ll ksm(ll base,ll p)  //听说pow不是很好用?于是来了一个快速幂
{
	ll ans=1;
	while(p)
	{
		if(p&1) ans*=base;
		base*=base;
		p>>=1;
	}
	return ans;
}


int main()
{
	//ios::sync_with_stdio(false);
	ll T;cin>>T;
	while(T--)
	{
		ll n,h,r;
		cin>>n>>h>>r;
		init(n);
		for(ll i=1;i<=n;++i)
		{
			cin>>x[i]>>y[i]>>z[i];
			if(z[i]+r>=h) f1[++cnt1]=i; //f1记录在顶面的点
			if(z[i]-r<=0) f2[++cnt2]=i; //f2记录在底面的点
		}
		for(ll i=1;i<=n;++i)
		{
			for(ll j=i+1;j<=n;++j)
			{
				ll fx=find(i);ll fy=find(j);
				if(fx==fy) continue; //如果两个点已经相通,跳过
				ll dist=ksm(x[i]-x[j],2)+ksm(y[i]-y[j],2)+ksm(z[i]-z[j],2); //计算距离
				ll rdist=r*r*4; //两点间的半径
				if(dist<=rdist) f[fx]=fy;
			}
		}
		bool flag=0;
		//cout<<cnt1<<" "<<cnt2<<endl;
		for(ll i=1;i<=cnt1;++i)
		{
			for(ll j=1;j<=cnt2;++j)
			{
				if(find(f1[i])==find(f2[j]))
				{
					flag=1; 
					break;
				}
			}
		}
		if(flag) cout<<"Yes"<<endl;
		else cout<<"No"<<endl;
	}
 	return 0;		
}

类似的问题,也可以用并查集实现。

其他方法(搜索)

听说两种搜索方法都可以用。可以考虑链式前向星建图,再进行dfs和bfs等。就不放代码了。(因为我还没写)

代码中值得注意的地方

  • 注意f数组的初始化
  • 重新读入下一轮数据时,将f1,f2数组的计数变量重新初始化。
  • 没了
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值