题目描述
你正在一个 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)
 - 我们需要判断是否存在一条从左边界到右边界的路径,全程不进入任何圆形禁区
 - 如果存在,还要找到最北的入口和出口
 
关键观察
- 屏障效应 :如果存在一组相连的圆形成从上边界到下边界的连续屏障,则无法穿越
 - 边界阻挡:即使没有完整屏障,某些圆可能阻挡左侧或右侧的特定区域,影响入口和出口的选择
 
解题思路
第一步:判断可行性(是否存在通路)
使用并查集 (Union-Find\texttt{Union-Find}Union-Find)来判断是否存在从上边界到下边界的连续屏障:
- 合并相交的圆:如果两个圆的圆心距离小于等于半径之和,它们相交,属于同一连通分量
 - 标记边界接触 :
- 如果一个圆与上边界 (y=1000y = 1000y=1000)相交或相切,标记该连通分量接触上边界
 - 如果一个圆与下边界 (y=0y = 0y=0)相交或相切,标记该连通分量接触下边界
 
 - 检查屏障 :如果存在某个连通分量同时接触上边界和下边界 ,则形成完整屏障,输出 
IMPOSSIBLE 
第二步:寻找最北入口和出口
使用 BFS\texttt{BFS}BFS(广度优先搜索)从接触上边界的圆开始扩展阻挡区域:
- 初始化:将所有接触上边界的圆加入队列
 - BFS扩展:不断将与当前圆相交的未访问圆加入队列
 - 更新边界阻挡 :
- 对于每个访问到的圆,检查是否与左边界 (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 ]
 - 更新右边界阻挡的最南端为这些交点范围的最小值
 
 
 - 对于每个访问到的圆,检查是否与左边界 (x=0x = 0x=0)相交
 - 确定入口和出口 :
- 最北入口 = 左边界阻挡的最南端(如果无阻挡则为 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;
}