题目描述
你站在房间中央,手里拿着一堆大小不一的圆盘。你可以随机选择一个圆盘并扔到地板上,重复此过程直到手上没有圆盘。圆盘落地后,有些圆盘会被后来的圆盘部分(或完全)覆盖。
你的任务是:从上方观察,计算所有圆盘可见部分的周长总和。
图 1 展示了圆盘相互覆盖的情况(此处省略图片,但可想象为若干圆形互相重叠,上层圆盘遮挡下层圆盘的部分区域)。
输入格式
第一行是一个整数 T≤100T \\le 100T≤100,表示测试用例的数量。
每个测试用例的第一行是一个整数 N≤100N \\le 100N≤100,表示圆盘的数量。
接下来 NNN 行,每行包含三个浮点数:rk≤100000r_k \\le 100000rk≤100000(半径),以及 −100000≤xk,yk≤100000-100000 \\le x_k, y_k \\le 100000−100000≤xk,yk≤100000(圆心坐标)。
如果两个浮点数的绝对差小于 10−1010^{-10}10−10,则认为它们相等。
输出格式
对于每个测试用例,输出一行一个浮点数,表示从上方可见的圆盘周长总和,四舍五入保留三位小数。
输出必须恰好有三位小数。
样例
输入
3
1
10 0 0
2
5 0 0
10 0 0
2
100 110
输出
62.832
62.832
10.472
题目分析
本题的核心是:给定若干个圆盘,按照输入顺序依次落到地面上(后输入的圆盘落在先输入的圆盘之上)。最终,从上往下看时,每个圆盘只能看到没有被任何后来的圆盘遮挡的部分。我们需要计算所有圆盘可见部分的总周长。
关键点分析
遮挡关系:如果圆盘 iii 先落下,圆盘 jjj 后落下(j>ij > ij>i),则圆盘 jjj 可能遮挡圆盘 iii 的一部分。遮挡的区域是圆盘 iii 上的一段圆弧,该弧上的点同时位于圆盘 jjj 的内部。
完全覆盖:
- 如果后来的圆盘 jjj 完全包含圆盘 iii(即 d+ri≤rjd + r_i \\le r_jd+ri≤rj),则圆盘 iii 的整个圆周都被遮挡,可见周长为 000。
- 如果后来的圆盘 jjj 完全被圆盘 iii 包含(即 d+rj≤rid + r_j \\le r_id+rj≤ri),则圆盘 jjj 不会遮挡圆盘 iii 的任何部分(因为它在圆盘 iii 的内部,不会接触到圆盘 iii 的边界)。
相交情况:若两圆相交(∣ri−rj∣<d<ri+rj|r_i – r_j| < d < r_i + r_j∣ri−rj∣<d<ri+rj),则圆盘 iii 上有一段连续的圆弧被圆盘 jjj 遮挡。该圆弧的圆心角(相对于圆心 OiO_iOi 到 OjO_jOj 的方向)可以通过余弦定理求出。
多个遮挡区间合并:一个圆盘可能被多个后来的圆盘遮挡,这些遮挡区间可能相互重叠。我们需要将这些区间合并,得到总遮挡弧度,然后用 2π2\\pi2π 减去它得到可见弧度,再乘以半径得到可见周长。
解题思路
1. 几何建模
设圆盘 iii 的圆心为 OiO_iOi,半径为 RiR_iRi;圆盘 jjj(j>ij > ij>i)的圆心为 OjO_jOj,半径为 rjr_jrj。
圆心距:d=∣OiOj∣d = |O_i O_j|d=∣OiOj∣。
如果两圆相交,则圆盘 iii 被圆盘 jjj 遮挡的弧段对应的半角 θ\\thetaθ 满足:
cosθ=Ri2+d2−rj22Rid
\\cos\\theta = \\frac{R_i^2 + d^2 – r_j^2}{2 R_i d}
cosθ=2RidRi2+d2−rj2
其中 θ∈[0,π]\\theta \\in [0, \\pi]θ∈[0,π]。这个公式来源于三角形 OiOjPO_i O_j POiOjP(PPP 为两圆交点),由余弦定理得到。
遮挡区间相对于向量 OiOj→\\overrightarrow{O_i O_j}OiOj 的方向角 α=atan2(dy,dx)\\alpha = \\text{atan2}(dy, dx)α=atan2(dy,dx) 对称分布,即区间为 [α−θ,α+θ][\\alpha – \\theta, \\alpha + \\theta][α−θ,α+θ]。
2. 区间标准化与合并
由于极角区间可能跨越 000 或 2π2\\pi2π,我们需要将其标准化到 [0,2π)[0, 2\\pi)[0,2π)。具体做法是:
- 若 l<0l < 0l<0,拆分为 [l+2π,2π)[l + 2\\pi, 2\\pi)[l+2π,2π) 和 [0,r][0, r][0,r]。
- 若 r>2πr > 2\\pir>2π,拆分为 [l,2π)[l, 2\\pi)[l,2π) 和 [0,r−2π][0, r – 2\\pi][0,r−2π]。
然后对所有区间按左端点排序,依次合并重叠或相邻的区间,得到总覆盖弧度。
3. 算法流程
对于每个测试用例:
- 创建一个空的区间列表 blocked\\textit{blocked}blocked,用于存储所有被遮挡的弧段。
- 遍历所有后来的圆盘 jjj(j>ij > ij>i):
- 计算圆心距 ddd。
- 如果 d+Ri≤rj+epsd + R_i \\le r_j + \\text{eps}d+Ri≤rj+eps:圆盘 jjj 完全包含圆盘 iii,则圆盘 iii 完全不可见,将 blocked\\textit{blocked}blocked 设为 [0,2π)[0, 2\\pi)[0,2π) 并跳出循环。
- 如果 d+rj≤Ri+epsd + r_j \\le R_i + \\text{eps}d+rj≤Ri+eps 或 d≥Ri+rj−epsd \\ge R_i + r_j – \\text{eps}d≥Ri+rj−eps:无遮挡,跳过。
- 否则,计算方向角 α\\alphaα 和半角 θ\\thetaθ,得到区间 [α−θ,α+θ][\\alpha – \\theta, \\alpha + \\theta][α−θ,α+θ],标准化并加入 blocked\\textit{blocked}blocked。
- 合并 blocked\\textit{blocked}blocked 中的所有区间,得到总遮挡弧度 covered\\textit{covered}covered。
- 可见周长 =(2π−covered)×Ri= (2\\pi – \\textit{covered}) \\times R_i=(2π−covered)×Ri,累加到 ans\\textit{ans}ans。
4. 复杂度分析
- 每个测试用例有 NNN 个圆盘,对每个圆盘 iii 需要检查 N−iN – iN−i 个后来的圆盘。
- 每次检查 O(1)O(1)O(1) 时间。
- 区间合并:最多 O(N)O(N)O(N) 个区间,排序 O(NlogN)O(N \\log N)O(NlogN)。
- 总时间复杂度:O(N2+N2logN)=O(N2logN)O(N^2 + N^2 \\log N) = O(N^2 \\log N)O(N2+N2logN)=O(N2logN),在 N≤100N \\le 100N≤100 时完全可行。
代码实现
// Sweet Dream
// UVa ID: 10969
// Verdict: Accepted
// Submission Date: 2026-06-11
// 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-10;
struct Circle { double x, y, r; };
double normAngle(double a) {
while (a < 0) a += 2 * PI;
while (a >= 2 * PI) a -= 2 * PI;
return a;
}
vector<pair<double, double>> addInterval(vector<pair<double, double>> &intervals, double l, double r) {
if (r – l > 2 * PI + EPS) {
intervals.emplace_back(0, 2 * PI);
return intervals;
}
l = normAngle(l);
r = normAngle(r);
if (l < r) intervals.emplace_back(l, r);
else {
intervals.emplace_back(l, 2 * PI);
intervals.emplace_back(0, r);
}
return intervals;
}
double mergeAndGetCovered(vector<pair<double, double>> &intervals) {
if (intervals.empty()) return 0.0;
sort(intervals.begin(), intervals.end());
double covered = 0.0;
double curL = intervals[0].first, curR = intervals[0].second;
for (size_t i = 1; i < intervals.size(); i++) {
if (intervals[i].first <= curR + EPS) {
curR = max(curR, intervals[i].second);
} else {
covered += curR – curL;
curL = intervals[i].first;
curR = intervals[i].second;
}
}
covered += curR – curL;
return covered;
}
int main() {
int T;
scanf("%d", &T);
while (T—) {
int N;
scanf("%d", &N);
vector<Circle> c(N);
for (int i = 0; i < N; i++) {
scanf("%lf%lf%lf", &c[i].r, &c[i].x, &c[i].y);
}
double ans = 0.0;
for (int i = 0; i < N; i++) {
vector<pair<double, double>> blocked;
for (int j = i + 1; j < N; j++) {
double dx = c[j].x – c[i].x, dy = c[j].y – c[i].y;
double d = sqrt(dx * dx + dy * dy);
double R = c[i].r, r = c[j].r;
if (d + R <= r + EPS) {
blocked.clear();
blocked.emplace_back(0, 2 * PI);
break;
}
if (d + r <= R + EPS || d >= R + r – EPS)
continue;
double ang = atan2(dy, dx);
double theta = acos((R * R + d * d – r * r) / (2 * R * d));
addInterval(blocked, ang – theta, ang + theta);
}
double covered = mergeAndGetCovered(blocked);
ans += (2 * PI – covered) * c[i].r;
}
printf("%.3lf\\n", ans);
}
return 0;
}
总结
本题是一个计算几何与区间合并相结合的典型问题。关键点包括:
技巧总结:
- 将几何问题转化为区间覆盖问题,是处理圆形遮挡问题的常用方法。
- 对于极角区间,标准化到 [0,2π)[0, 2\\pi)[0,2π) 后,跨越 2π2\\pi2π 的情况可以通过拆分区间来处理。
- 按照输入顺序(即掉落顺序)处理遮挡关系,可以自然地保证后落的圆盘覆盖先落的圆盘。
本题虽小,但涵盖了计算几何、区间算法和精度控制等多个知识点,是一道很好的综合练习题。

![第5章,[Win32 章节] :边框绘制函数(六)-171主机测评](https://www.171host.com/wp-content/uploads/2026/08/20260822144238-6a89b55eb002b-220x150.png)
