UVa 11177 Fighting against a Polygonal Monster
本文提出了一种求解以原点为圆心的圆与凸多边形相交面积的最小半径的算法。通过二分搜索确定满足相交面积≥R的最小半径,每次迭代中计算多边形每条边与圆的相交面积并累加。利用几何性质将多边形分解为三角形,分别处理完全在圆内、完全在圆外及与圆相交的情况。算法复杂度为O(n log(精度^-1)),适用于顶点数≤50的多边形。示例输入输出验证了算法的正确性。
题目描述
你正在对抗一个多边形怪物。你有一件特殊武器,可以发射激光束。激光束可以看作是沿着 zzz 轴的圆柱体,因此从 XYXYXY 平面上方看,激光束实际上是一个以 (0,0)(0,0)(0,0) 为中心的圆(你无法改变圆心位置)。怪物是一个凸多边形(你可以把它想象成一个非常薄的棱柱),它有 nnn 个顶点,并且原点 (0,0)(0,0)(0,0) 严格位于怪物内部(不在边界上)。
已知当激光束与怪物的公共面积至少为 RRR 时,怪物死亡。由于激光束越大消耗的能量越多,你希望找到激光束的最小半径。
编写程序计算最小半径。保证 (0≤R≤A)(0 \leq R \leq A)(0≤R≤A),其中 AAA 是怪物的面积。
输入格式
最多 101010 组测试数据。每组数据以一个整数 nnn(3≤n≤503 \leq n \leq 503≤n≤50)和一个浮点数 RRR 开始,接下来 nnn 行每行两个实数,表示怪物的顶点坐标。顶点按逆时针或顺时针顺序给出。最后一组数据后是一个单独的 000,表示输入结束。
输出格式
对于每组数据,输出案例编号和最小半径,保留两位小数。输入保证即使使用双精度浮点数,微小的精度误差也不会影响可见输出。
示例输入
3 1.60
-1 -1
1 -1
0 1
0
示例输出
Case 1: 0.93
题目分析
本题的核心是计算以原点为圆心的圆与给定凸多边形的相交面积,并找到使得该面积至少为 RRR 的最小半径 rrr。
关键性质
- 圆心固定:激光束的圆心始终在原点 (0,0)(0,0)(0,0)。
- 多边形包含原点:原点严格位于多边形内部,这意味着对于任何半径 rrr,圆与多边形的相交区域都是连续的。
- 凸多边形:多边形的凸性简化了几何计算。
- 单调性:半径 rrr 越大,相交面积越大,因此可以通过二分搜索求解。
解题思路
1. 二分搜索框架
由于相交面积 S(r)S(r)S(r) 是 rrr 的单调递增函数,我们可以使用二分搜索来找到满足 S(r)≥RS(r) \geq RS(r)≥R 的最小 rrr。
- 搜索范围:从 000 到足够大的值(如 10510^5105)。
- 迭代次数:进行固定次数的二分迭代(如 606060 次),以获得足够的精度。
- 判断条件:计算当前半径 midmidmid 下的相交面积,若 ≥R\geq R≥R 则缩小右边界,否则增大左边界。
2. 计算相交面积
对于凸多边形与圆的相交面积,我们可以将多边形分解为以原点为顶点的三角形,分别计算每个三角形与圆的相交面积,然后求和。
对于三角形 △OAB\triangle OAB△OAB(其中 OOO 为原点,AAA 和 BBB 为多边形相邻顶点),其与半径为 rrr 的圆的相交面积可以通过以下情况计算:
情况 1: AAA 和 BBB 都在圆内
此时三角形完全在圆内,相交面积就是三角形 △OAB\triangle OAB△OAB 的面积:
S△OAB=12∣OA⃗×OB⃗∣ S_{\triangle OAB} = \frac{1}{2} |\vec{OA} \times \vec{OB}| S△OAB=21∣OA×OB∣
情况 2: AAA 和 BBB 都在圆外,且线段 ABABAB 与圆无交点
此时三角形与圆的相交部分是一个扇形,其圆心角为 ∠AOB\angle AOB∠AOB,面积为:
Ssector=12θr2 S_{\text{sector}} = \frac{1}{2} \theta r^2 Ssector=21θr2
其中 θ\thetaθ 为向量 OA⃗\vec{OA}OA 与 OB⃗\vec{OB}OB 的夹角(0∼2π0 \sim 2\pi0∼2π)。
情况 3: AAA 在圆内,BBB 在圆外(或反之)
设线段 ABABAB 与圆的交点为 PPP(靠近 BBB 侧)。则相交面积由两部分组成:
- 三角形 △OAP\triangle OAP△OAP 的面积(完全在圆内)。
- 扇形 ∠POB\angle POB∠POB 的面积。
情况 4: AAA 和 BBB 都在圆外,且线段 ABABAB 与圆有两个交点 P1P_1P1 和 P2P_2P2
此时相交面积由三部分组成:
- 扇形 ∠AOP1\angle AOP_1∠AOP1 的面积。
- 三角形 △OP1P2\triangle OP_1P_2△OP1P2 的面积。
- 扇形 ∠P2OB\angle P_2OB∠P2OB 的面积。
3. 几何计算细节
- 线段与圆的交点:通过求解直线参数方程与圆的方程得到交点,再判断交点是否在线段上。
- 向量夹角:使用 atan2\text{atan2}atan2 函数计算夹角,并调整到 [0,2π)[0, 2\pi)[0,2π) 范围。
- 方向处理:确保多边形顶点顺序为逆时针,以保证面积计算为正。
算法步骤
- 读入多边形顶点,确保其为逆时针顺序。
- 二分搜索半径 rrr:
- 对于每个候选半径 midmidmid,调用函数计算圆与多边形的相交面积。
- 相交面积计算:遍历多边形的每条边,计算对应的三角形 △OAB\triangle OAB△OAB 与圆的相交面积,累加得到总面积。
- 根据总面积与 RRR 的比较调整搜索边界。
- 输出结果,保留两位小数。
时间复杂度
- 每个测试案例:二分搜索 O(log(O(\log(O(log(精度−1))^{-1}))−1)) 次,每次计算相交面积需要 O(n)O(n)O(n) 时间,其中 nnn 为顶点数。
- 总体复杂度:O(nlog(O(n \log(O(nlog(精度−1))^{-1}))−1)),对于 n≤50n \leq 50n≤50 和 606060 次二分迭代,完全可行。
代码实现
// Fighting against a Polygonal Monster
// UVa ID: 11177
// Verdict: Accepted
// Submission Date: 2026-01-14
// UVa Run Time: 0.000s
//
// 版权所有(C)2026,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const double PI = acos(-1.0);
const double EPS = 1e-9;
int dcmp(double x) { return fabs(x) < EPS ? 0 : (x < 0 ? -1 : 1); }
struct Point {
double x, y;
Point(double x = 0, double y = 0) : x(x), y(y) {}
Point operator - (const Point &p) const { return Point(x - p.x, y - p.y); }
Point operator + (const Point &p) const { return Point(x + p.x, y + p.y); }
Point operator * (double d) const { return Point(x * d, y * d); }
double operator * (const Point &p) const { return x * p.y - y * p.x; } // 叉积
double dot(const Point &p) const { return x * p.x + y * p.y; }
double len() const { return sqrt(x * x + y * y); }
double len2() const { return x * x + y * y; }
};
// 向量夹角(0 ~ 2π)
double angleBetween(Point a, Point b) {
double dot = a.dot(b);
double det = a * b;
double ang = atan2(det, dot);
if (ang < 0) ang += 2 * PI;
return ang;
}
// 扇形面积:圆心角 theta,半径 r
double sectorArea(double theta, double r) {
return 0.5 * theta * r * r;
}
// 三角形 OAB 的有向面积
double triangleArea(Point a, Point b) {
return fabs(a * b) / 2.0;
}
// 计算三角形 OAB 与半径为 r 的圆的相交面积
double triangleCircleIntersection(Point a, Point b, double r) {
double da = a.len(), db = b.len();
// 两个点都在圆内
if (dcmp(da - r) <= 0 && dcmp(db - r) <= 0) return triangleArea(a, b);
// 两个点都在圆外且线段与圆无交点
Point d = b - a;
double A = d.dot(d);
double B = 2 * d.dot(a);
double C = a.dot(a) - r * r;
double delta = B * B - 4 * A * C;
if (dcmp(delta) <= 0) {
double theta = angleBetween(a, b);
return sectorArea(theta, r);
}
double t1 = (-B - sqrt(delta)) / (2 * A);
double t2 = (-B + sqrt(delta)) / (2 * A);
if (t2 < 0 || t1 > 1) {
double theta = angleBetween(a, b);
return sectorArea(theta, r);
}
t1 = max(t1, 0.0);
t2 = min(t2, 1.0);
Point p1 = a + d * t1;
Point p2 = a + d * t2;
double res = 0;
// A -> p1
if (dcmp(da - r) <= 0) {
res += triangleArea(a, p1);
} else {
double theta = angleBetween(a, p1);
res += sectorArea(theta, r);
}
// p1 -> p2
res += triangleArea(p1, p2);
// p2 -> B
if (dcmp(db - r) <= 0) {
res += triangleArea(p2, b);
} else {
double theta = angleBetween(p2, b);
res += sectorArea(theta, r);
}
return res;
}
// 计算圆与多边形的相交面积
double areaCirclePolygon(const vector<Point> &poly, double r) {
double area = 0;
int n = poly.size();
for (int i = 0; i < n; i++) {
Point a = poly[i], b = poly[(i + 1) % n];
area += triangleCircleIntersection(a, b, r);
}
return area;
}
int main() {
int n, caseNo = 1;
double R;
while (cin >> n && n) {
cin >> R;
vector<Point> poly(n);
for (int i = 0; i < n; i++) cin >> poly[i].x >> poly[i].y;
// 确保多边形面积为正(逆时针)
double polyArea = 0;
for (int i = 0; i < n; i++) polyArea += poly[i] * poly[(i + 1) % n];
if (dcmp(polyArea) < 0) reverse(poly.begin(), poly.end());
// 二分搜索半径
double left = 0, right = 1e5;
for (int iter = 0; iter < 60; iter++) {
double mid = (left + right) / 2;
double interArea = areaCirclePolygon(poly, mid);
if (interArea >= R) right = mid;
else left = mid;
}
cout << fixed << setprecision(2) << "Case " << caseNo++ << ": " << left << endl;
}
return 0;
}
总结
本题是一道典型的计算几何题目,结合了二分搜索和几何面积计算。关键在于正确处理圆与多边形的相交面积,特别是各种边界情况的分类讨论。通过将多边形分解为三角形,并分别计算每个三角形与圆的相交面积,可以避免复杂的积分计算,实现简洁高效。
核心要点:
- 利用二分搜索处理单调性问题。
- 将多边形分解为以原点为顶点的三角形。
- 分类计算三角形与圆的相交面积(三角形、扇形及其组合)。
- 注意浮点数精度处理,使用 EPS\texttt{EPS}EPS 进行比较。
此解法在精度和效率上均能满足题目要求,是解决此类问题的标准方法。
更多推荐



所有评论(0)