原题:POJ 3608 Bridge Across Islands(POJ 评测机太老仅 C++98,强烈推荐 vjudge 上的镜像 OpenJ_Bailian-3851)。本题是两凸多边形最近距离的旋转卡壳模板题,但有一个反直觉的「单调推进准则」选择 —— 第一版贪心在 OJ 上 TLE,调试改 cross 单调推进后又 WA,最终发现单次调用对偶覆盖不完整,必须 (A, B) 和 (B, A) 各跑一次。
题目大意
太平洋中央有一个王国,领土由两座岛屿组成,被洋流冲成两个凸多边形。国王要造一座桥连两岛,问两岛边界的最小距离是多少。
输入
多组测试用例。每组先给两整数 和 ( ),接下来 行各给一对坐标描述第一个凸多边形的一个顶点(已按某种顺序给出,CW 或 CCW),再接 行给第二个凸多边形顶点。坐标 。
一行 表示输入结束。
输出
每组输出最小距离,误差 。
样例
1 | 4 4 |
输出:
1 | 1.00000 |
思路讲解
一句话
两凸多边形最近距离 = 旋转卡壳 + facing 弧双指针 ;推进准则用「cross(a, b') 角度差」、不要用「dis2 < dis1 顶点-边距离单峰」(贪心在背面弧会卡住);最终结果必须 (A, B) 和 (B, A) 各调一次取 才覆盖完整。
把最近距离归并到「顶点 → 线段」
两个不相交的凸多边形之间,最小距离的实现位置只有三种:
| 特征对 | 是否独立 | 备注 |
|---|---|---|
| 顶点 - 边内部 | ✓ 主路径 | 从某顶点向另一多边形某条边作垂线,垂足落在边内 |
| 顶点 - 顶点 | 退化 | 当 的垂足落在 外,点-线段距离自动退成点-点距离 |
| 边 - 边 | 退化 | 仅当两边平行才可能;端点也取得到,被「顶点对线段」覆盖 |
关键不变量:枚举所有 对,算点-线段距离取 ,三种情形一网打尽。
facing 弧 + 单调推进
固定 上一个顶点 ,凸性下 上「真正可能成为离 最近的那条边」有且仅有一条 —— 称它为 的 facing edge。形象点:从 看过去, 朝向 那半弧上正对 的那条边,其它边要么被它挡住、要么转到背面去了。
把 沿 走 CCW 一圈,那条 facing 边在 上的位置是**「短程单调、长程往返」**:
-
短程( 走在 朝 那半弧上):facing 边在 上 单调推进(不回退)
-
长程( 越过 的背面弧): 没有新边可选,facing 边只能沿同一段 facing 弧 退回来
-
整圈轨迹 = 「推过去 → 退回来」 = 往返
算法直接好处:两个指针不需要走完一整圈,各自只走完自己的 facing 弧就够了,总步数 。
初始 facing pair:A 最低 + B 最高
按代码取:
-
= 上 最小的点的下标
-
= 上 最大的点的下标
几何上 看上去就是 、 看下去就是 —— 这一对是初始 facing pair,也是 / 各自 facing 弧的一个端点。
不过这个初始只在「 整体在 上方」时直观对齐;其它分布下( 在右、 在左之类)yMnA + yMxB 不一定是真 facing pair。没关系 —— 推进准则用 cross 角度差驱动后,初始位置不正确也能在 步内追上正确的对踵 pair,所以这个朴素初始化就够了。
cross(a, b') 推进准则(核心)
设:
-
: 在 的 CCW 出边方向
-
: 在 的 CCW 出边 反向(因为 的 facing 弧逆 的 CCW 序前进)
推进准则:
几何意义:初始 facing pair 处 和 都大约朝 ,几乎平行同向;两者沿各自多边形 CCW 推进时都向 CCW 方向旋转(凸性)。 衡量两者角度差:
-
: 在 的 CCW 一侧 角度落后 让 推一步追上
-
:反之,让 推一步
为什么这样不会 TLE:每步刚好推一个指针、各自只能 forward。 走完 一圈、 走完 一圈就停,总步数 。背面弧问题自动消失 ——
cross一直按角度差驱动,根本不依赖「dis 谁更小」这种局部贪心。
为什么必须 (A, B) + (B, A) 各调一次
单次 cal_min_dis_two_convex(A, B) 只覆盖**「 顶点 → 边」**这组对偶配置 —— 也就是「枚举 上每个顶点,找 上对应的 facing 边」。
但反对称几何里答案可能取在另一边:细长 戳向粗 的边中间, 的端点垂足落在 的边内部 —— 这种「 顶点 - 边」抓得到;反过来「 顶点 - 边」抓不到。
解决:把同一份代码以 (B, A) 参数顺序再跑一遍,补上反向对偶,最后两次取 :
`c++
DB ans = INFINITY;
ans = min(ans, cal_min_dis_two_convex(A, B));
ans = min(ans, cal_min_dis_two_convex(B, A));
1 |
|
// 两凸多边形最近距离(旋转卡壳, O(n + m))
// 单次调用只覆盖「A 边 → B 顶点」配置,
// 调用方需以 (A, B) 和 (B, A) 各调一次取 min。
template
DB cal_min_dis_two_convex(vector<Point
reorder_polygon(A);
reorder_polygon(B);
ll yMnA = min_element(all(A), [](const Point<T> &a, const Point<T> &b) {
return a.y < b.y;
}) - A.begin();
ll yMxB = max_element(all(B), [](const Point<T> &a, const Point<T> &b) {
return a.y < b.y;
}) - B.begin();
DB ans = INFINITY;
ll eA = yMnA, eB = yMxB;
for (int _ = 0; _ < SZ(A) + SZ(B); ++_) {
auto check = [&]() {
auto vecA = A[(eA + 1) % SZ(A)] - A[eA];
auto vecB = B[(eB + 1) % SZ(B)] - B[eB];
return cross(vecA, vecB) > 0;
};
while (check()) ++eB, eB %= SZ(B);
ans = min(ans, distanceSS(Line(A[(eA + 1) % SZ(A)], A[eA]),
Line(B[(eB + 1) % SZ(B)], B[eB])));
eA = (eA + 1) % SZ(A);
}
return ans;
}
1 |
|
Bridge_Across_Islands_html_demos.zip
mentor.tex 编译产物 PDF —— me/mentor 5 步推导对话录(题面 / 三类特征对 / facing 弧 / 旋转卡壳骨架 / cross 推进准则)