题解:P3958 [NOIP2017 提高组] 奶酪
ZSYhaouuan · · 题解
涉及到集合的合并操作,可以使用并查集这个数据结构。
容易发现,可以把一个通道看成一个集合,答案就是这些集合能到达上下两端的通道数量了。问题就在于,如何对于洞口进行合并。
怎么样的两个洞可以合并呢?可以发现,如果两个圆的圆心的直线距离小于等于两个半径,即可以合并。
然后接着考虑怎么合并。我的想法是优先遍历编号小的洞,如果能合并就将祖先更新为它;之后再遇到别的洞就让他们祖先更新成自己祖先。这样一来,所有通道的有关信息都在编号最小的洞里了。
考虑设置两个数组,分别表示此结点及所在通道是否到达上方和到达下方。更新的时候,把祖先被更新的信息的洞的上下信息更新到自己祖先去即可。
最后遍历哪一个上下两方都有通道,大功告成!
AC代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
//fa为祖先信息
ll t, fa[10000 + 100];
//top代表到达顶上的,down代表到达底下的
bool top[10000 + 100], down[100000 + 10];
struct node {
ll x, y, z;
} p[10000 + 100];
ll fin(ll x) {//板子
if (fa[x] == x) return x;
else return fa[x] = fin(fa[x]);
}
int main() {
cin >> t;
for (ll o = 1; o <= t; o++) {
//多测不清空,原地见祖宗。。。
memset(top, 0, sizeof top);
memset(down, 0, sizeof down);
for (ll i = 1; i <= 10000; i++) p[i].x = p[i].y = p[i].z = 0;
ll n, h, r, ans = 0;//ans总计答案数量
cin >> n >> h >> r;
for (ll i = 1; i <= n; i++) fa[i] = i;//并查集初始化
for (ll i = 1; i <= n; i++) {
ll x, y, z;
cin >> x >> y >> z;
p[i].x = x;
p[i].y = y;
p[i].z = z;
//一个可有可无的特判:如果只有一个,则看他能否到上面也能到下面
if (n == 1) {
ll rp = 0;
if (p[i].z <= r) rp++;
if (p[i].z >= h - r) rp++;
if (rp == 2) cout << "Yes\n";
else cout << "No\n";
}
bool flag = 1;//是否第一次连接
if (p[i].z <= r) down[i] = 1;
if (p[i].z >= h - r) top[i] = 1;
//依次遍历之前所有的洞
for (ll j = 1; j < i; j++) {
//p就是直线距离,勾股定理求
double q = sqrt((p[i].x - p[j].x) * (p[i].x - p[j].x) + (p[i].y - p[j].y) * (p[i].y - p[j].y) + (p[i].z - p[j].z) * (p[i].z - p[j].z));
if (q <= 2 * r) {//可以合并
ll t1 = fin(i), t2 = fin(j);
if (flag) {//第一次连接
flag = 0;
fa[t1] = t2;
//根据定义,把自己的信息传递给祖先信息
if (!top[t2]) top[t2] = top[t1];
if (!down[t2]) down[t2] = down[t1];
} else {
//根据定义,把别人的信息传递给自己祖先信息
if (!top[t1]) {
top[t1] = top[t2];
top[t2] = 0;
}
if (!down[t1]) {
down[t1] = down[t2];
down[t2] = 0;
}
fa[t2] = t1;
}
//能到上面和下面
if (top[t2] && down[t2]) ans++;
}
}
}
//答案输出
if (n != 1) {
if (ans > 0) cout << "Yes\n";
else cout << "No\n";
}
}
return 0;
}
代码较长,理解起来也还行。