UVa 11853 Paintball

题目描述

你正在一个 1000×10001000 \times 10001000×1000 的正方形场地上玩彩弹游戏。场地上有若干对手躲在树后,每个对手位于 (x,y)(x, y)(x,y) 位置,并且可以朝任意方向发射彩弹,攻击范围为 rrr。如果你在移动过程中进入任何对手的攻击范围,就会被击中。

场地左下角为 (0,0)(0,0)(0,0),左上角为 (0,1000)(0,1000)(0,1000)。你必须从场地的左侧边(介于西南角和西北角之间)进入,从右侧边(介于东南角和东北角之间)离开。

对于每个场景,判断是否能完成穿越。如果能,输出进入点和离开点的坐标(保留两位小数),且要选择最北的入口;如果不能,输出 IMPOSSIBLE

输入格式

输入包含多个场景。每个场景的第一行是整数 n≤1000n \leq 1000n≤1000,表示对手数量。接下来 nnn 行,每行包含三个实数:对手的 (x,y)(x, y)(x,y) 坐标和攻击半径 rrr。

输出格式

对于每个场景,输出四个实数(保留两位小数)表示进入和离开坐标,或者输出 IMPOSSIBLE

样例输入

复制代码
3
500 500 499
0 0 999
1000 1000 200

样例输出

复制代码
0.00 1000.00 1000.00 800.00

题目分析

问题本质

这是一个平面几何连通性问题:

  • 每个对手相当于一个圆形禁区 (圆心 (x,y)(x, y)(x,y),半径 rrr)
  • 我们需要判断是否存在一条从左边界到右边界的路径,全程不进入任何圆形禁区
  • 如果存在,还要找到最北的入口和出口

关键观察

  1. 屏障效应 :如果存在一组相连的圆形成从上边界到下边界的连续屏障,则无法穿越
  2. 边界阻挡:即使没有完整屏障,某些圆可能阻挡左侧或右侧的特定区域,影响入口和出口的选择

解题思路

第一步:判断可行性(是否存在通路)

使用并查集 (Union-Find\texttt{Union-Find}Union-Find)来判断是否存在从上边界到下边界的连续屏障:

  1. 合并相交的圆:如果两个圆的圆心距离小于等于半径之和,它们相交,属于同一连通分量
  2. 标记边界接触
    • 如果一个圆与上边界 (y=1000y = 1000y=1000)相交或相切,标记该连通分量接触上边界
    • 如果一个圆与下边界 (y=0y = 0y=0)相交或相切,标记该连通分量接触下边界
  3. 检查屏障 :如果存在某个连通分量同时接触上边界和下边界 ,则形成完整屏障,输出 IMPOSSIBLE

第二步:寻找最北入口和出口

使用 BFS\texttt{BFS}BFS(广度优先搜索)从接触上边界的圆开始扩展阻挡区域:

  1. 初始化:将所有接触上边界的圆加入队列
  2. BFS扩展:不断将与当前圆相交的未访问圆加入队列
  3. 更新边界阻挡
    • 对于每个访问到的圆,检查是否与左边界 (x=0x = 0x=0)相交
      • 如果相交,计算交点范围 [y−r2−x2,y+r2−x2][y - \sqrt{r^2 - x^2}, y + \sqrt{r^2 - x^2}][y−r2−x2 ,y+r2−x2 ]
      • 更新左边界阻挡的最南端为这些交点范围的最小值
    • 同样检查是否与右边界 (x=1000x = 1000x=1000)相交
      • 如果相交,计算交点范围 [y−r2−(1000−x)2,y+r2−(1000−x)2][y - \sqrt{r^2 - (1000-x)^2}, y + \sqrt{r^2 - (1000-x)^2}][y−r2−(1000−x)2 ,y+r2−(1000−x)2 ]
      • 更新右边界阻挡的最南端为这些交点范围的最小值
  4. 确定入口和出口
    • 最北入口 = 左边界阻挡的最南端(如果无阻挡则为 1000.001000.001000.00)
    • 最北出口 = 右边界阻挡的最南端(如果无阻挡则为 1000.001000.001000.00)

算法正确性证明

可行性判断

  • 如果存在连通分量同时接触上下边界,则这些圆形成连续屏障,无法穿越
  • 否则,至少存在一条通路可以穿越场地

最北入口确定

  • 所有接触上边界的圆及其相连圆构成顶部阻挡区域
  • 这个区域在左边界上的最南端点就是最北的安全入口
  • 因为任何比这个点更北的入口都会被顶部阻挡区域拦截

参考代码

cpp 复制代码
// Paintball
// UVa ID: 11853
// Verdict: Accepted
// Submission Date: 2025-10-30
// UVa Run Time: 0.010s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

#include <bits/stdc++.h>

using namespace std;

const double eps = 1e-8;
const double W = 1000.0;

struct Circle {
    double x, y, r;
};

double dist(double x1, double y1, double x2, double y2) {
    return hypot(x1 - x2, y1 - y2);
}

bool circlesIntersect(const Circle& a, const Circle& b) {
    return dist(a.x, a.y, b.x, b.y) < a.r + b.r + eps;
}

int parent[1005];

int find(int x) {
    if (parent[x] != x) parent[x] = find(parent[x]);
    return parent[x];
}

void unite(int a, int b) {
    a = find(a);
    b = find(b);
    if (a != b) parent[a] = b;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n;
    while (cin >> n) {
        vector<Circle> enemies(n);
        for (int i = 0; i < n; i++) {
            cin >> enemies[i].x >> enemies[i].y >> enemies[i].r;
        }

        // ===== 第一步:用并查集判断是否存在通路 =====
        for (int i = 0; i < n; i++) parent[i] = i;
        
        // 合并相交的圆
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if (circlesIntersect(enemies[i], enemies[j])) {
                    unite(i, j);
                }
            }
        }

        // 检查每个连通分量是否同时接触上边界和下边界
        vector<bool> top(n, false), bottom(n, false);
        for (int i = 0; i < n; i++) {
            int root = find(i);
            if (enemies[i].y + enemies[i].r > W - eps) top[root] = true;
            if (enemies[i].y - enemies[i].r < eps) bottom[root] = true;
        }

        bool impossible = false;
        for (int i = 0; i < n; i++) {
            if (parent[i] == i && top[i] && bottom[i]) {
                impossible = true;
                break;
            }
        }

        if (impossible) {
            cout << "IMPOSSIBLE\n";
            continue;
        }

        // ===== 第二步:寻找最北入口和出口 =====
        vector<bool> visited(n, false);
        double leftBlock = W;  // 左边界阻挡的最南端,初始为最北
        double rightBlock = W; // 右边界阻挡的最南端,初始为最北

        // BFS 从接触上边界的圆开始扩展阻挡区域
        vector<int> queue;
        for (int i = 0; i < n; i++) {
            if (enemies[i].y + enemies[i].r > W - eps) { // 接触上边界
                visited[i] = true;
                queue.push_back(i);
            }
        }

        // BFS 扩展阻挡区域
        for (int idx = 0; idx < queue.size(); idx++) {
            int u = queue[idx];
            const Circle& c = enemies[u];

            // 更新左边界阻挡
            if (c.x - c.r < eps) { // 接触左边界
                double dy = sqrt(max(0.0, c.r * c.r - c.x * c.x)); // 避免数值误差
                double bottomY = c.y - dy;
                if (bottomY < leftBlock) {
                    leftBlock = bottomY;
                }
            }

            // 更新右边界阻挡
            if (c.x + c.r > W - eps) { // 接触右边界
                double dx = W - c.x;
                double dy = sqrt(max(0.0, c.r * c.r - dx * dx)); // 避免数值误差
                double bottomY = c.y - dy;
                if (bottomY < rightBlock) {
                    rightBlock = bottomY;
                }
            }

            // 扩展相邻圆
            for (int v = 0; v < n; v++) {
                if (!visited[v] && circlesIntersect(c, enemies[v])) {
                    visited[v] = true;
                    queue.push_back(v);
                }
            }
        }

        // 确定入口和出口坐标
        double enterY, exitY;
        
        if (leftBlock > W - eps) {
            // 没有圆阻挡左边界,入口可以是 (0, 1000)
            enterY = W;
        } else {
            // 阻挡区域最南端就是最北入口
            enterY = leftBlock;
            // 如果阻挡区域延伸到 y = 0 以下,入口只能是0
            if (enterY < 0) enterY = 0;
        }
        
        if (rightBlock > W - eps) {
            // 没有圆阻挡右边界,出口可以是 (1000, 1000)
            exitY = W;
        } else {
            // 阻挡区域最南端就是最北出口
            exitY = rightBlock;
            // 如果阻挡区域延伸到 y = 0 以下,出口只能是 0
            if (exitY < 0) exitY = 0;
        }

        // 输出结果
        cout << fixed << setprecision(2)
             << 0.00 << " " << enterY << " "
             << W << " " << exitY << "\n";
    }

    return 0;
}
相关推荐
Theodore_10226 小时前
深度学习(10)模型评估、训练与选择
人工智能·深度学习·算法·机器学习·计算机视觉
五条凪6 小时前
Verilog-Eval-v1基准测试集搭建指南
开发语言·人工智能·算法·语言模型
是店小二呀7 小时前
从“算法思维”到“算子思维”:我在昇腾AI开发中的认知跃迁
人工智能·算法
仰泳的熊猫7 小时前
LeetCode:72. 超级次方
数据结构·c++·算法·leetcode
闻缺陷则喜何志丹7 小时前
【超音速专利 CN118134841A】一种光伏产品缺陷检测AI深度学习算法
人工智能·深度学习·算法·专利·光伏·超音速
爱看科技8 小时前
微美全息(NASDAQ:WIMI)容错量子计算赋能,大规模机器学习模型高效量子算法获突破
算法·机器学习·量子计算
_dindong8 小时前
牛客101:递归/回溯
数据结构·c++·笔记·学习·算法·leetcode·深度优先
刃神太酷啦8 小时前
力扣校招算法通关:双指针技巧全场景拆解 —— 从数组操作到环检测的高效解题范式
java·c语言·数据结构·c++·算法·leetcode·职场和发展
西瓜树枝9 小时前
遗传算法与属性约简:原理、代码与参数配置
算法