题目描述
你正在一个 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;
}