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)
- 我们需要判断是否存在一条从左边界到右边界的路径,全程不进入任何圆形禁区
- 如果存在,还要找到最北的入口和出口
关键观察
- 屏障效应:如果存在一组相连的圆形成从上边界到下边界的连续屏障,则无法穿越
- 边界阻挡:即使没有完整屏障,某些圆可能阻挡左侧或右侧的特定区域,影响入口和出口的选择
解题思路
第一步:判断可行性(是否存在通路)
使用并查集(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)
 
算法正确性证明
可行性判断
- 如果存在连通分量同时接触上下边界,则这些圆形成连续屏障,无法穿越
- 否则,至少存在一条通路可以穿越场地
最北入口确定
- 所有接触上边界的圆及其相连圆构成顶部阻挡区域
- 这个区域在左边界上的最南端点就是最北的安全入口
- 因为任何比这个点更北的入口都会被顶部阻挡区域拦截
参考代码
// 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 以下,入口只能是0if (enterY < 0) enterY = 0;}if (rightBlock > W - eps) {// 没有圆阻挡右边界,出口可以是 (1000, 1000)exitY = W;} else {// 阻挡区域最南端就是最北出口exitY = rightBlock;// 如果阻挡区域延伸到 y = 0 以下,出口只能是 0if (exitY < 0) exitY = 0;}// 输出结果cout << fixed << setprecision(2)<< 0.00 << " " << enterY << " "<< W << " " << exitY << "\n";}return 0;
}
