17 KiB
B题问题一 交会定位区域的直径与圆覆盖
问题一可以转化为二维半平面交问题:每次带误差的示向度给出一个前向角域,多次测量的交集就是定位区域。对于非空有界定位多边形,直径等于最远顶点对的距离。**以这个直径为直径的圆不一定覆盖定位区域。**判断是否能覆盖,应检验最远顶点对中点到所有顶点的距离,或直接计算最小包围圆。
代码、合成算例与官方演练观测均已实现和保存。本解答只解决问题一;在官方问题3演练中采集同一频道的三个检测点,用于检验交会计算,没有执行全域搜索清除任务,未使用正式测试。
一、题目假设和模型边界
检测点为 $S_i=(s_{ix},s_{iy})$,测得示向度为 $\theta_i$,误差半角为 $\delta=1^\circ$。角度从正东开始逆时针计算。同一频道对应同一干扰源;位置不变时重复检测不会消除误差。
本问求附录2交会法所定义的多边形,因此主模型只取前向角域之交,不额外加入1800米圆域或1500米接收上界。这些先验可用于后续搜索定位,但加入圆盘后边界可能包含圆弧,不能仍按原问题的纯多边形处理。本程序也不以图像视窗作为真实边界。
模拟器示向度保留两位小数,正文规定误差在 \pm 1^\circ 内。本次主计算使用 $\pm 1^\circ$;如需把独立舍入误差也纳入保守预算,可用 --rounding-margin-deg 0.005 显式追加半角,输出会记录实际误差半角。这是额外的工程裕量,不是题面规定。
二、从测向角域得到线性约束
设二维叉积 $[a,b]=a_xb_y-a_yb_x$,令
u_i^-=(\cos(\theta_i-\delta),\sin(\theta_i-\delta)),\qquad
u_i^+=(\cos(\theta_i+\delta),\sin(\theta_i+\delta)).
未知源位置 X=(x,y) 必须满足
[u_i^-,X-S_i]\ge0,\qquad [u_i^+,X-S_i]\le0.
这两条不等式选择两条边界射线之间的前向角域,而不是无限延伸的两条直线。写为标准形式 $a_jx+b_jy\le c_j$:
\begin{aligned}
\sin(\theta_i-\delta)x-\cos(\theta_i-\delta)y
&\le \sin(\theta_i-\delta)s_{ix}-\cos(\theta_i-\delta)s_{iy},\\
-\sin(\theta_i+\delta)x+\cos(\theta_i+\delta)y
&\le-\sin(\theta_i+\delta)s_{ix}+\cos(\theta_i+\delta)s_{iy}.
\end{aligned}
程序另外保留 u_i\cdot(X-S_i)\ge 0 的前向约束。当 0<\delta<90^\circ 时它是冗余的,但可让 \delta=0 的扩展情形正确表示射线,而不是整条直线。实现支持 $0\le\delta<90^\circ$,题目算例始终使用 $1^\circ$。
定位区域为
P=\bigcap_{i=1}^{n}W_i=\{X:AX\le b\}.
通过向量而不是简单的角度大小比较处理约束,可自然处理 359^\circ 与 0^\circ 跨界问题。
三、可复现的算法
- 验证输入为有限数值,将检测点平移至其均值附近并按尺度归一化,减小大坐标带来的抵消误差。
- 构造所有半平面;枚举每对不平行边界直线的交点。
- 只保留满足全部不等式的交点;去重后用单调链算法求凸包,并保留点、线段等退化结果。
- 判断有无非零方向
d满足 $Ad\le 0$。若存在,任一可行点沿该方向无限前进仍可行,区域无界,直径为无穷大。 - 对有界非空区域,枚举凸包顶点对,得到直径及一对最远顶点。
- 用任意一组最远顶点对中点与半径
D/2检验圆覆盖;失败时返回圆外顶点及最大超出距离。用 Welzl 随机增量法求最小包围圆,输出 20 米清除半径余量,以区分“直径圆覆盖”和“保证清除”。
非空前向角域的交集不会包含一条双向无限直线,即它是尖的多面集。这保证非空区域存在极点,所以在本模型中可以通过是否存在可行边界交点来判空。无观测时需单独返回整个平面无界,不能依据“没有顶点”误判为空集。
二维非平凡衰退锥若存在,至少有一条边界方向与某个约束边界平行。因此检查每条边界的两个切向方向是否满足 $Ad\le 0$,即可判定无界性。无界情形中输出的有限顶点仅是区域的极点,不构成完整有限多边形。
伪代码如下:
输入同一频道的测量列表 M
若 M 为空:返回无界
H ← 将每次测量转换为前向角域的半平面约束
V ← 所有边界对交点中满足 H 的点
若 V 为空:返回空集
若存在非零 d 满足 H 的齐次约束 A d <= 0:返回无界
V ← 去重并排序为凸包
(D, A, B) ← V 中的最远顶点对
O ← (A+B)/2
覆盖成立 <- max_{v in V} ||v-O|| <= D/2
返回 V、D、A、B、O、覆盖判断和最小包围圆
设半平面数为 $k$(本实现中 $k=3n$),最终顶点数为 $m$。枚举交点并检验全部约束需要 $O(k^3)$,直径计算需要 $O(m^2)$,无界性检查需要 $O(k^2)$。可行交点排序的代价上界为 $O(k^2\log k)$,临时交点存储上界为 $O(k^2)$。新增直径圆证据只需 O(m) 遍历。
最小包围圆改为 Welzl 随机增量算法:先打乱点顺序;已有圆包含新点则保留,否则强制新点成为边界点并解更小子问题;二维最多需要 3 个支撑点。3 个强制边界点必须用外接圆,不能误用这 3 点的无约束最小圆。实现以显式栈代替递归,防止大量点超过 Python 递归深度。固定随机种子使输出可复现,理论期望线性指随机排列下的期望,不是该固定种子的最坏复杂度保证。对外函数内部也做坐标归一化,最后重新计算所有点到输出圆心的最大距离,保持数值包含性。旧 O(m^4) 支撑点枚举保留为独立测试基准。
半平面交与直径仍采用容易审查的小规模算法,未为少量测点引入更复杂的旋转卡壳。大规模在线数据可后续替换半平面交,不能把MEC局部提速等同于整个求解器线性复杂度。
四、为什么只枚举顶点对就能得到直径
有界凸多边形内任意两点可表示为顶点的凸组合:
x=\sum_i\lambda_i v_i,\quad y=\sum_j\mu_jv_j,\quad
\lambda_i,\mu_j\ge0,\quad\sum_i\lambda_i=\sum_j\mu_j=1.
由三角不等式,
\lVert x-y\rVert=\left\lVert\sum_{i,j}\lambda_i\mu_j(v_i-v_j)\right\rVert
\le\sum_{i,j}\lambda_i\mu_j\lVert v_i-v_j\rVert
\le\max_{i,j}\lVert v_i-v_j\rVert.
最远顶点对本身又属于区域,因此上界可以达到:
\boxed{D(P)=\max_{i,j}\lVert v_i-v_j\rVert.}
点的直径为0,线段的直径为端点距离。对空集,本程序约定直径未定义,输出null;对无界区域,输出 diameter_infinite=true,不把Infinity写入不兼容标准的JSON数值。
五、直径圆能否覆盖的充分必要条件
取任意最远顶点对 $A,B$,距离为 $D$,令 $O=(A+B)/2$。如果半径为 D/2 的某个圆能够同时包含 A 和 $B$,则
D=\lVert A-B\rVert\le\lVert A-O'\rVert+\lVert O'-B\rVert\le D.
两个不等式只能同时取等号,因此其圆心只能是O。又因为圆盘是凸集,包含全部顶点等价于包含整个多边形。所以
\boxed{\text{存在直径为 }D\text{ 的覆盖圆}\iff
\max_{v\in V}\lVert v-(A+B)/2\rVert\le D/2.}
不必尝试移动直径圆,也不必尝试其他最远点对:若这个唯一可能的圆心不满足覆盖,就不存在直径为 D 的覆盖圆。最小包围圆半径 \rho 满足 $\rho\ge D/2$,当且仅当 \rho=D/2 时存在这样的覆盖圆。
代码的 diameter_circle.max_excess_m 为 $\max_{v\in V}\lVert v-O\rVert-D/2$;超出数值容差时返回 witness_vertex 和从 0 开始的 outside_vertex_indices。因此输出既有判断,也有可独立复核的反例。判定中使用 coverage_tolerance_m,边界附近的布尔结果属于浮点容差下结论。
保证清除使用 MEC 半径加数值余量 $\varepsilon_m$,再与 20 米比较。clearance_certificate.remaining_margin_m 对应 $20-(\rho+\varepsilon_m)$;非负时支持当前圆心清除,负数时不作此保证。这里的证书以观测误差界正确、真源属于 P 和浮点几何计算可靠为前提,不是实际清除成功记录。数学上所有可行清除中心构成 $\bigcap_{v\in V}B(v,20)$,MEC 圆心是充分选择;后续可在此集合中另选距离机器狗最近的点。
六、满足题设 \pm 1^\circ 的具体反例
取三个检测点及示向度:
| 检测点 | x(米) | y(米) | 示向度 |
|---|---|---|---|
S_1 |
-1000 |
0 |
1^\circ |
S_2 |
540 |
-500\sqrt{3} |
121^\circ |
S_3 |
520 |
520\sqrt{3} |
241^\circ |
三个角域的交集恰为等边三角形,顶点是 $(0,0)$、$(40,0)$、$(20,20\sqrt{3})$。每个角域的一条边界对应三角形的一条边,另一条边界在三角形外侧,不切去三角形内部。可将真实源设在三角形中心 $(20,20\sqrt{3}/3)$,其真实方位与上述读数的偏差均小于 $1^\circ$,且三个检测距离都小于 1500 米。因此这不是脱离测向模型的任意图形反例。
它的直径为 D=40 米;直径圆半径为 20 米。以底边两端为最远点对,圆心为 $(20,0)$,到第三个顶点的距离是 20\sqrt{3}\approx 34.6410 米,因此不能覆盖。其最小包围圆为
O_*=(20,20\sqrt{3}/3),\qquad \rho_*=40/\sqrt{3}\approx 23.0940\,\text{米}>20\,\text{米}.
图中蓝色为定位区域,红色虚线为直径圆,绿色实线为最小包围圆。黑色加号只在合成场景中表示已知真源。
七、官方演练数据验证
2026年9月10日,界面明确为“问题3 演练 测试”后,通过公开HTTP接口采集。案例编码为 U5ZW-P9KN-TCAG-R7ST,选取频道9。
| 检测点 | x(米) | y(米) | 示向度 |
|---|---|---|---|
S_1 |
0 |
0 |
131.52^\circ |
S_2 |
-613.761885 |
391.530775 |
34.92^\circ |
S_3 |
-314.272134 |
656.683353 |
227.19^\circ |
重复在 S_1 测量一次仍返回 $131.52^\circ$。这次重复观测只用于核查原地误差固定,没有作为新的独立测点加入模型。
使用 \pm 1^\circ 得到四边形,其顶点按逆时针顺序为:
| 顶点 | x(米) | y(米) |
|---|---|---|
| V1 | −460.698306 | 502.411776 |
| V2 | −456.424236 | 497.750714 |
| V3 | −440.569893 | 515.476604 |
| V4 | −441.370356 | 516.413163 |
计算结果:D=23.996723米,最远点对为V1和V3;直径圆中心为(−450.634100,508.944190),半径11.998362米,但最远顶点到这个圆心的距离为12.602364米,所以该圆不能覆盖区域。最小包围圆中心为(−449.976955,507.952835),半径12.068583米。
共发送14个被接受的请求:1次进入、12次检测、1次退出,最终虚拟时间293.602198秒。日志末尾为 /exit、accepted=true、exit_reason=user_exit;界面显示测试正常结束。没有调用 /clear,所以这里不能将演练结束解读为“清除了所有源”。官方没有公开该源的坐标真值,本例验证的是观测交会计算、接口适配和几何自洽性,不声称验证了真实位置误差。
原始请求响应保存在 output/q1/practice/*_actions.json,保留了复算所需的请求ID、位置、频道与时间;三点观测及其计算结果保存在 *_observations.json。没有读取或解密模拟器隐藏场景数据。
八、运行方式和代码入口
核心算法仅用Python标准库,支持Python3.10及以上;工程依赖仅 requests(测试另需 pytest)。在项目根目录运行:
uv run python -m mcm26b.geometry examples/q1/triangle.json -o output/q1/triangle_recomputed.json
uv run mcm26b q1 examples/q1/normal.json
uv run python -m mcm26b.geometry examples/q1/normal.json --rounding-margin-deg 0.005
uv run pytest
uv run python scripts/build_q1_demo.py
uv run python scripts/benchmark_q1.py
输入JSON的最小格式:
{
"measurements": [
{"x": 0, "y": 0, "bearing_deg": 131.52, "half_angle_deg": 1},
{"x": -613.7618853216438, "y": 391.53077545248124, "bearing_deg": 34.92},
{"x": -314.2721344680094, "y": 656.6833525352392, "bearing_deg": 227.19}
]
}
省略 half_angle_deg 时默认为1。输出包含区域状态、顶点、直径、最远点对、直径圆覆盖判断、反例证据、最小包围圆和清除余量。CLI的 error_policy 记录追加裕量与每条观测最终半角;默认追加0,不改变输入口径。tolerance_m 是归一化容差换算回米的尺度;核心算法不接收真源坐标,样例中的 target 只用于合成数据的可视化与测试。空集和无界区域不输出有限圆及清除证书。
| 文件 | 职责 |
|---|---|
src/mcm26b/geometry/localization.py |
测向角域、半平面、区域分类、直径和包围圆 |
src/mcm26b/geometry/planning.py |
问题二首次测向的距离保证候选圆盘;独立于问题一模型 |
src/mcm26b/geometry/__main__.py、src/mcm26b/cli.py |
JSON命令行输入输出 |
tests/test_geometry.py |
解析反例、随机几何、独立裁剪对照与退化测试 |
tests/test_practice_replay.py |
离线复算演练计时与观测结果,不连接模拟器 |
tests/test_geometry_certificates.py |
增量圆与穷举法对照、覆盖证据、清除阈值、舍入选项及首次选点证书 |
scripts/benchmark_q1.py |
局部性能对比与官方演练误差界敏感性复算 |
scripts/capture_practice.py |
在已经启动且已核验的演练中采集少量测向 |
scripts/build_q1_demo.py |
用Python算法生成案例、结果和交互图数据 |
scripts/assets/q1.template.html |
可视化源模板;不另写一套求解算法 |
output/q1/question1.html |
可直接打开的离线交互式模拟,所有数据已内嵌 |
如需再次采集,先在模拟器明确启动“问题3演练测试”,等待接口就绪,读取案例编码,然后运行:
uv run python scripts/capture_practice.py --robot-id 你的参赛队号 --practice-confirmed --case-code 界面中的演练案例编码
接口协议没有返回演练/正式模式的字段,--practice-confirmed 是操作者确认已核验界面的显式参数,不能替代界面核查。采集脚本没有启动测试的功能,没有正式模式选项,只会调用 /enter、/measure、/exit。禁止在正式测试会话中运行。
九、验证范围和数值限制
20项自动化测试通过,包括100组随机有界案例、60组与独立矩形裁剪实现的对照、250组增量圆与穷举圆对照、1500个共圆点及重复点/共线点、平移旋转缩放不变性,以及已保存演练日志的逐动作计时复算。新增测试还覆盖清除距离20米临界值、显式舍入选项、首次安全候选圆盘边界和零半径情形。已测试一组有限但直径超过10亿米的近乎平行测向,确认不会因为显示范围而被截断成小区域。
本次离线优化报告保存在 output/q1/optimization_report.json。同一演练观测的敏感性结果如下,原始记录不改写:
| 误差半角 | 直径 / 米 | MEC半径 / 米 | 直径圆最大超出 / 米 | 含数值裕量的清除余量 / 米 |
|---|---|---|---|---|
| $1^\circ$(默认题设) | 23.996723 | 12.068583 | 0.604002 | 7.931415 |
| $1.005^\circ$(额外舍入) | 24.119088 | 12.130377 | 0.608486 | 7.869621 |
在本机60个共圆点的局部基准中,旧MEC枚举中位约306.5毫秒,新算法约0.096毫秒;4点时旧法约0.022毫秒、新法约0.029毫秒,小规模没有提速优势。这些是特定输入的局部实测,不是普遍倍率保证,也不代表网络、全清闭环或官方成绩。三点演练的完整问题一求解中位约0.099毫秒。运行基准脚本可以重新生成当前机器的报告。
浮点计算不是精确算术。实现使用归一化位置容差 $10^{-9}$、平行行列式阈值 10^{-12} 和较严格的衰退方向角度容差。极端近乎平行、差异接近机器精度或接近退化的输入可能需要高精度几何谓词;结果接近临界阈值时应做容差敏感性检查,而不把最后若干小数位当作严格证明。正常题设坐标和测向精度下,本次案例远离这些数值极限。
交互图数据全部由同一Python求解器生成。已在最小DOM测试环境中检查32个“案例/测点数量/视图”组合,未发现缺失控件或NaN等非法图形坐标;并对360与736像素、明暗两种配色的SVG作静态图形检查。浏览器工具对本地文件URL的访问被策略拦截,因此没有声称完成真实浏览器的HTML/CSS布局验证。离线页面和对话内可视化均已生成;静态PNG可作为直接查看的补充。
几何算法参考:CGAL最小包围圆定义与Welzl增量方法;McGill旋转卡壳说明。本次代码仅用Python标准库,不依赖CGAL。

