美团笔试题 UVa 11853 Paintball 战场 黑洞的最大半径 二分答案 并查集 DFS

题目链接:黑洞的最大半径
题目描述:

小明初始在二维平面上的原点,他要去往 ( x , y ) (x,y) (x,y),给定 n n n个黑洞的中心坐标,求使得小明能够到达 ( x , y ) (x,y) (x,y)的黑洞最大半径,小明不能进入任意一个黑洞的所属圆(含边界)中。

题解:

首先我们发现答案明显满足单调性即:对于 r 0 r_0 r0如果飞船能够通过,那么当半径减小时依然可以通过,如果对于 r 0 r_0 r0不能通过,半径增大时依然不能通过。这满足二分答案的前提要求。我们可以对可能的半径进行二分答案(由于题目并没有说答案是否可能是小数,代码中按照整数考虑,对于小数,只需要在二分的时候设置精度 e p s eps eps即可)。
接下来我们需要考虑 c h e c k check check函数的书写,我们可以通过并查集来维护相交的圆,将相交的圆合并在一起,同时维护圆能到达的最上方坐标,最下方坐标,最左边坐标,最右边坐标。再将所有相交的圆合并之后我们会得到一系列圆的不同集合,此时我们只需要判断是否存在一个圆的集合满足:(上边界在终点之上且下边界在原点之下)或者(左边界在原点之左且右边界在原点之右)如果有这样一个圆的集合满足改点,那么飞船就一定不能通过,同时如果所有的圆的集合都不满足这一点,那么飞船就可以通过。

代码:

#include <bits/stdc++.h>

const int MAXN = 1e3 + 10;

using namespace std;

int n, x, y, l, r;
vector<pair<int, int>> holes;

struct Info
{
    int u, d, l, r;
}info[MAXN];
int h[MAXN];

int find(int x) { return h[x] == x ? x : h[x] = find(h[x]); }
void join(int x, int y)
{
    int fx = find(x);
    int fy = find(y);
    h[fx] = fy;
    info[fy].u = max(info[fy].u, info[fx].u);
    info[fy].d = min(info[fy].d, info[fy].d);
    info[fy].l = min(info[fy].l, info[fx].l);
    info[fy].r = max(info[fy].r, info[fx].r);
}
double squareDistance(pair<int, int> a, pair<int, int> b)
{
    return (a.first - b.first) * (a.first - b.first) + (a.second - b.second) * (a.second - b.second);
}
bool check(int radius)
{
    for (int i = 0; i < n; i++) {
        h[i] = i;
        info[i] = {holes[i].second + radius, holes[i].second - radius,
                   holes[i].first - radius, holes[i].first + radius};
    }
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++)
        if (squareDistance(holes[i], holes[j]) <= 4 * radius * radius) {
            join(i, j);
        }
    } // 此处不能通过按照坐标排序方法来解决,即使要排序也应该按照距离上一个点的距离来排序,这样排序复杂度不见得会下降
    for (int i = 0; i < n;i ++) {
        int father = find(i);
        if ((info[father].u >= y && info[father].d <= 0) ||
            (info[father].l <= 0 && info[father].r >= x)) { return false; }
    }
    return true;
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n;
    holes.resize(n);
    for (int i = 0; i < n; i++) { cin >> holes[i].first >> holes[i].second; }
    cin >> x >> y;
    r = min(x, y);
    while(l <= r) {
        int mid = l + (r - l) / 2;
        if (check(mid)) { l = mid + 1; }
        else { r = mid - 1; }
    }
    cout << r << endl;
    return 0;
}

2023.1.26更新:
后面看紫书发现该题目与 U V a 11853 UVa 11853 UVa11853类似,于是将两道题目整合到一起。
U V a 11853 UVa 11853 UVa11853题目描述:

给定平面上 n n n个圆,问能否在不进入圆的情况下从平面的左边界到达平面的右边界(平面的大小为 1000 × 1000 1000\times1000 1000×1000),如果可以通过则需要输出进入点与离开点,如果有多个进入点和离开点都能到达,则应该让进入点和离开点坐标尽可能的靠上。

紫书上的题解:

这里用上面的并查集也可判断是否能够到达,但是如何找到更加靠上的进入点与离开点,刘汝佳在紫书中提供的方法更好,整体思路是类似的,只是这里不需要从左下角到右上角,所以判断条件变成了是否存在相交的圆这些圆触碰到了上边界与下边界;如果存在,那么就不能到达右边界,如果不存在,那么就可以达到右边界。刘汝佳指出可以从与上边界相交的圆出发,依次经过相交的圆,如果过程中发现经过的圆到达了下边界,那么此时就不能到达。
接下来考虑如何找出最靠上的进入点与离开点。实际上在上述遍历的过程中如果一个圆触碰到了左边界,那么该圆与左边界交点的上方均不能作为起点,碰到右边界也是同理,于是在上述遍历过程中,每次发现圆碰到了左右边界,则更新进入点与离开点的坐标即可。

代码:

#include <bits/stdc++.h>

const int MAXN = 1000;

using namespace std;

int n;
double leftPosition, rightPosition;
bool vis[MAXN];

struct Circle
{
    double x, y, r;
    Circle() {}
    Circle(double x, double y, double r) : x(x), y(y), r(r) {}
}circle[MAXN];

bool connected(int a, int b)
{
    return hypot(circle[a].x - circle[b].x, circle[a].y - circle[b].y) < circle[a].r + circle[b].r;
}

bool touchBottom(int now)
{
    vis[now] = true;
    if (circle[now].y - circle[now].r < 0) { return true; }
    if (circle[now].x - circle[now].r < 0) {
        leftPosition = min(leftPosition, circle[now].y - \
                         (sqrt(pow(circle[now].r, 2) - pow(circle[now].x, 2))));
    }
    if (circle[now].x + circle[now].r > 1000) {
        rightPosition = min(rightPosition, circle[now].y - \
                           (sqrt(pow(circle[now].r, 2) - pow(1000 - circle[now].x, 2))));
    }
    for (int i = 0; i < n; i++) {
        if (vis[i] || !connected(now, i)) { continue; }
        if (touchBottom(i)) { return true; }
    }
    return false;
}

int main()
{
    ios::sync_with_stdio(false);
    input:
    while(cin >> n) {
        memset(vis, 0, sizeof(vis));
        leftPosition = rightPosition = 1000;
        for (int i = 0; i < n; i++) {
            cin >> circle[i].x >> circle[i].y >> circle[i].r;
        }
        for (int i = 0; i < n; i++) {
            if (!vis[i] && 1000 - circle[i].y < circle[i].r) {
                if (touchBottom(i)) {
                    cout << "IMPOSSIBLE" << endl;
                    goto input;
                }
            }
        }
        cout << "0.00" << " " << fixed << setprecision(2) << leftPosition << " "
             << "1000.00" << " " << fixed << setprecision(2) << rightPosition << endl;
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值