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)=(x1−x2)2+(y1−y2)2+(z1−z2)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=(x1−x2)2+(y1−y2)2+(z1−z2)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数组的计数变量重新初始化。
- 没了