0%

ICPC 2024 江西省赛 Neuvillette Circling

题目大意

CF gym 105231 I - Neuvillette Circling | rating 2100 | 2024 (ICPC) 江西省赛 武大 WHU 命题组 | n100n \le 100 ,时限 2 秒

NN 个宝藏在平面上 (xi,yi)(x_i, y_i) ,任意两个宝藏之间有一条线段(路径),每条路径上有一个丘丘人——它可能出现在线段上的任意一点(位置对抗性)。Neuvillette 画一个圆(圆心任选、半径要最小),圆上或圆内的丘丘人被秒。

要求:无论这些丘丘人具体站在各自线段的哪个位置,他都能至少干掉 mm 个。对 m=1,2,,(n2)m = 1, 2, \dots, \binom{n}{2} 分别输出最小半径。

数据范围

  • 2n1002 \le n \le 100

  • 103xi,yi103-10^3 \le x_i, y_i \le 10^3 ,坐标全是整数

  • 保证无两点重合 / 无三点共线

  • 答案精度 10610^{-6}

样例 ( n=5n = 5 )

输入:

1
2
3
4
5
6
5
-6 -9
2 4
9 2
1 -1
-4 -5

输出(10 行):

1
2
3
4
5
6
7
8
9
10
2.23606798
4.28602124
4.28602124
7.38241153
7.38241153
7.38241153
9.30053762
9.30053762
9.30053762
9.30053762

思路讲解

一句话

对抗性建模 \to 「包住整段」等价于「包住两端点」(圆盘是凸集) \to 圆里有 kk 个原始点就保证 (k2)\binom{k}{2} 条边的丘丘人都被秒 \to 题目变成 对每个 kk 求最小 kk 点覆盖圆。MEC 定理给出候选圆只有 O(n3)O(n^3) 个:两点直径 + 三点外接。总复杂度 O(n4)O(n^4)n=100n = 100 下宽松到不用卡常。

Step 1:对抗性建模——把「覆盖一条边」翻译成「包住两个点」

题目说丘丘人可能出现在线段任意一点,又要求无论它在哪都得能杀掉。这是经典对抗设定:玩家先确定圆,丘丘人才挑位置。

「保证这条边上的丘丘人被秒」 \Longleftrightarrow整条线段在圆盘内

否则对手把丘丘人放在圆外那一点。

再用「圆盘是凸集」收一刀——圆盘内任意两点的连线段仍在圆盘内:

整条线段在圆盘内 \Longleftrightarrow 线段两端点都在圆盘内

这一步把「覆盖一条边」降级为「包住它的两个端点」,几何复杂度直接塌一档。

Step 2:从覆盖边到覆盖点—— (k2)\binom{k}{2} 平台结构

承接上一步。如果圆盘里有 kk 个原始点,这 kk 个点两两之间共 (k2)\binom{k}{2} 条边、其两端点都在圆内 \Rightarrow(k2)\binom{k}{2} 条边上的丘丘人全保证被秒

所以题目等价为:

对每个 k=2,3,,nk = 2, 3, \dots, n ,求「至少包住 kk 个原始点的最小圆」的半径 ansk[k]\mathrm{ans}_k[k]

输出时,给定 mm 找最小的 kmk_m 满足 (km2)m\binom{k_m}{2} \ge m ,那个槽的最小半径就是答案。代码里更优雅的写法:把每个候选圆按 (in_cir_cnt2)\binom{\text{in\_cir\_cnt}}{2} 直接落到 mn_rad[ans] 槽,最后做一遍 suffix-min 把「更大 mm 的最优半径」向左 propagate 到小 mm 上——殊途同归,省一步反查。

样例的 4 个圆刚好对应 k=2,3,4,5k = 2, 3, 4, 5 四个平台:

| kk | 圆 | 半径 ( ansk[k]\mathrm{ans}_k[k] ) | 管的 mm |

| — | — | — | — |

| 2 | 紫(最近对直径) | 2.236 | 1 |

| 3 | 红(3 点外接) | 4.286 | 2, 3 |

| 4 | 蓝(4 点 MEC) | 7.382 | 4, 5, 6 |

| 5 | 绿(全 5 点 MEC) | 9.301 | 7, 8, 9, 10 |

每个 kk(k2)(k12)=k1\binom{k}{2} - \binom{k-1}{2} = k - 1mm ,正是三角形数 1, 2, 3, 4, ……

Step 3:MEC 定理迁移——候选圆只有 O(n3)O(n^3)

MEC(minimum enclosing circle)的关键结构性事实:

MEC 定理nn 个点的 MEC ,边界圆周上恰好经过 2 或 3 个原始点。

这是 Welzl 随机增量算法的依据。

  • 「至少 2」:反证。如果边界上 0 或 1 个原始点,可以把圆心稍微挪一挪 ++ 半径稍减,所有包含点继续在内 \Rightarrow 半径更小,矛盾。

  • 「至多 3」:平面上不共线 3 点决定唯一外接圆,题面保证「无三点共线」就是为了避开退化。

关键迁移:本题最优圆 RR^* (最小 kk 点覆盖圆)也满足这条结构性质——同样的 shrink/move 论证:若 RR^* 边界 b1b \le 1 个原始点,可以缩小并保持「含 k\ge k 个点」不变 \Rightarrow 与最优矛盾。所以 RR^* 必定是某个 Sk|S| \ge k 的子集的 MEC,由 2 或 3 个原始点决定。

这一刀把「枚举对象」从点的子集翻转成了

候选圆集合:

  • (n2)\binom{n}{2}两点直径圆(任选两点作直径)

  • (n3)\binom{n}{3}三点外接圆(任选三点的 circumcircle)

规模 O(n3)O(n^3) ,对每个圆 O(n)O(n) 数它包了多少个原始点 \toO(n4)O(n^4)

Step 4:复杂度算账

n=100n = 100 时:

(1002)+(1003)=4950+1617001.67×105 候选圆\binom{100}{2} + \binom{100}{3} = 4\,950 + 161\,700 \approx 1.67 \times 10^5 \text{ 候选圆}

每圆 ×100\times 100 次数点 \to1.67×1071.67 \times 10^7 次基本运算。2 秒时限内相当宽松,连卡常都不需要。

Step 5:FP 精度坑——这题的 WA 点

枚举候选圆时,圆的 2 或 3 个定义点本应在边界上(数学上 pc2=r2|p - c|^2 = r^2 恰好相等)。但 circle_from_diameter / outcircle_triangle 算出来的 r = sqrt(d) / 2 后,squared(r) = r * r 经过 sqrt 一来再 square 一回的 round trip,得到的不再是精确的 d/4d/4 ,会有 ULP 级误差:

1
2
3
4
5
d  =  5: r*r = 1.2500000000000002,  d/4 = 1.25
d = 13: r*r = 3.2499999999999996, d/4 = 3.25 ← r*r < d/4,边界被判到圆外
d = 20: r*r = 5.000000000000001, d/4 = 5.0
d = 50: r*r = 12.500000000000002, d/4 = 12.5
d =113: r*r = 28.250000000000004, d/4 = 28.25

如果用 dist² <= r² 严格比较,当 r² 偶尔比真值小时,定义点被判到圆外(dist² > r²),漏算 \to in_cir_cnt 不对 \to mn_rad[m] 拿不到本该的候选半径 \to WA。

样例 d=20d = 20 那个紫圆恰好走在 +ε+\varepsilon 一侧(<= 成立、定义点被算入),样例侥幸过;但测试数据里只要撞一个 d=13d = 13 这类形状,定义这个 diameter circle 的 2 个原点就漏算,整个候选圆失效。

修法:用 sgn(dist² - r²) <= 0 容差判定。

1
2
3
4
5
// AC https://vjudge.net/solution/69993234 使用 sgn 避免精度问题
template<class U>
bool is_point_in_circle(const Point<U> &p) {
return sgn(distancePPNorm(p, c) - squared(r)) <= 0;
}

sgnx<ε|x| < \varepsilon 当作 0,吸收 sqrt \to square 的 ULP 抖动。这个 patch 顺手同步到了 ICPC 几何模板 + espanso :Circle snippet,以后展开就自带正确版。

📎 动画与源码

完整带教对话录 PDF(mentor.tex 编译产物,含 6 步推导 + 我画的 4 个圆草图 + 全过程问答)在末尾附件区,可下载查看。

AC 代码

AC 提交链接(vjudge / Gym-105231I)

核心 Solve() 函数:枚举 (n3)\binom{n}{3} 个外接圆 + (n2)\binom{n}{2} 个直径圆,每个圆 O(n)O(n) 数包含点数,按 (in_cir_cnt2)\binom{\text{in\_cir\_cnt}}{2} 落到 mn_rad 槽,最后 suffix-min 把大 mm 的最优半径向左 propagate。

心路历程(WA → AC)

  • 第一次提交:大数据 WA,样例 AC。算法骨架完全对,suffix-min 也对,输入输出格式也对。

  • 卡点诊断思路:「样例 AC + 大数据 WA + 几何题」三联立基本就是 FP 精度在 boundary 上吞 byte。

  • 定位到 is_point_in_circledist² <= r² 严格比较——r = sqrt(d)/2squared(r) 跑了一趟 sqrt → square 的 round trip,对 ll 整数输入精度只够覆盖大约半数 dd 值。

  • 样例 d=20d = 20 那个紫圆(直径 (6,9)(4,5)(-6,-9) \leftrightarrow (-4,-5) )正好走在 +ε+\varepsilon 一侧,所以样例没暴露这个 bug;测试数据撞 d=13d = 13 类形状才漏算。

  • 修法return sgn(distancePPNorm(p, c) - squared(r)) <= 0; 把硬比较改成 sgn 容差判定,一行改完 AC。

  • 顺便把这个 patch 同步到 ICPC 几何模板 的 Circle struct + espanso :Circle snippet,以后再做几何题展开模板就自带正确版,省一次踩同一个坑。

附件

完整带教对话录(mentor.tex 编译产物 PDF,含 6 步推导)+ AC C++ 源码(.cpp.txt)直接附在下方,可下载查看。

完整带教对话录(mentor.tex 编译产物,6 步推导 + 第 1 步用户手画 4 个圆草图 + 全过程问答 + WA 诊断)

Neuvillette_Circling.cpp.txt