当前位置: 首页 > news >正文

【计算几何 | 那忘算 11】旋转卡壳(附详细证明)

专栏:再来一遍一定记住的算法_proMatheus的博客-CSDN博客


前置知识:

凸多边形的直径:凸多边形上任意两点间距离的最大值。

对踵(zhǒng)点对:如果过凸包上的两个点可以画一对平行直线,使凸包上的所有点都夹在两条平行线之间或落在平行线上,那么这两个点叫做一对对踵点

由于代码中需要大量向量辅助计算,不了解的话建议你先看这个。

0.前导

旋转卡壳算法是解决一些与凸包(给定点集的最小凸多边形)有关问题的高效算法,

计算时就像一对平行直线卡壳卡住凸包旋转而得名。

模板题:P1452 【模板】旋转卡壳 / [USACO03FALL] Beauty Contest G - 洛谷 (luogu.com.cn)

旋转卡壳的精髓,就是利用凸多边形的直径一定在对踵点处取得的特性,

以及一个凸多边形中对踵点最多只有 3n / 2 对的性质(n 和下文的 N 都指多边形的点数),

来将原来 O(N^2) 的枚举点对求凸多边形直径

优化成双指针单调移动的 O(N) 算法。

1.算法流程

首先题目给了 n 个点,想求凸包直径

我们就先求出凸包(用 Andrew 单调链算法,后面会细讲,这里先假设已经求出来了)。

接着我们了解定理(后面会证明,先记着):凸多边形的直径一定在对踵点处取得

又知道对踵点的定义:

过两个对踵点画两条平行直线,那凸包上的所有点都夹在两条平行线之间或落在平行线上

就先假设其中的一条直线是多边形的一条边 E,为了另一条平行直线也能 “括住” 所有点

我们就寻找离边 E 垂直距离最远的点,过点作一条与边 E 相平行的直线。

这样满足对踵点的定义,使凸包上的所有点都夹在两条平行线之间或落在平行线上

(因为垂直距离最远的点能 “括住” 的范围最大,并且因为是多边形,所以满足要求)

如图,确定边 AB,求出离 AB 垂直距离最远的点 E。

点对 {A,E} 和 {B,E} 都满足对踵点要求,也就都有可能成为多边形直径

然后我们想要逆时针枚举边,即下一条边是 BC,同样找出离 BC 垂直距离最远的点。

(点对 {B,F} 和 {C,F} 都满足对踵点要求,也就都有可能成为多边形直径

发现距离最远的点变成了 F,即 AB -> BC,E -> F,边和点都同样逆时针旋转

因为多边形是的,所以我们大胆猜测一下:距离最远点是单调移动的(不会顺时针 “后退”)

那么可以双指针一个枚举边一个枚举点,点的指针随着边的指针移动。

又因为枚举出来的对踵点最多只有 3n / 2 对,所以时间复杂度是 O(N) 的。

好像是做完了?但其实还有几个疑问:

(1)为什么凸多边形的直径只能在对踵点处取得?

(2)为什么距离最远点一定是单调移动的?

(3)为什么对踵点最多只有 3n / 2 对?

我们一个一个解决。

1.1 为什么凸多边形的直径只能在对踵点处取得

还是之前那个多边形,不过被我小小改了下:

假设直径是 AD,但很明显 {A,D} 不是对踵点

那应该怎么证明真正的对踵点 {G,D} 连成的线段 GD 长度比 AD 长?

首先根据余弦定理:

对于任意三角形,有:a^2=b^2+c^2+2bc\ cos\ \angle A

当 0<\angle A<90^{\circ}cos\ \angle A>0,有 a^2<b^2+c^2

当 \angle A=90^{\circ}cos\ \angle A=0,有 a^2=b^2+c^2

当 90^{\circ}<\angle A<180^{\circ}cos\ \angle A<0,有 a^2>b^2+c^2

当 \angle GAD\geq 90^{\circ} 时,AG^2+AD^2\leq GD^2GD 长度绝对大于 AD

而 \angle GAD<90^{\circ} 的情况,即 AG^2+AD^2> GD^2,好像并不能判断。

这时过 D 点作垂直 AG 与点 H:

由于 {A,D} 不是对踵点点 H 一定在 AG 偏点 A 那侧,即 AG 中点 P 的左侧。

(这里可以感性理解为 {A, D} 不是对踵点的原因是:在 ADG 这个锐角三角形中)

(D 点在偏向 A 点的那一侧,让 G 点能离的更远,更有可能当对踵点)

(可以自己尝试画几个图,在固定 AD 的情况下改变 AG 的角度)

因为 AH>HGAH^2+HD^2=AD^2HG^2+HD^2=GD^2

所以 AD<GD

这保证了整个算法的基础理论。

1.2 为什么距离最远点一定是单调移动的 & 3n / 2

(2)和(3)可以一起讲。

想象用两块平行木板夹住凸多边形旋转。固定左木板在顶点 A,右木板会接触对面的某个位置。

当左木板从 A 转到逆时针邻顶点 B 时,右木板的接触点只能向前移动,不能后退(凸性决定)。

在 A 到 B 的旋转过程中,右木板最多经历三种状态:

  • 接触一个顶点 C(形成 A - C 对)
  • 接触一条边 CD(此时 A 同时与 C、D 构成对踵对,因为木板可以贴着整条边)
  • 再接触下一个顶点 E

所以每个顶点最多产生 3 个对踵关系。

n 个顶点共 3n 个,但每对 (A,C) 被计算了两次(A 找 C 和 C 找 A),所以总数不超过 3n / 2。

关键在于凸性保证了接触点只能单调移动,同时保证只有 3n / 2 个点对,

保证时间复杂度 O(N)(可能还得乘个常数级但差不了多少)。

2.代码

可以先返回我开头给的那个链接学习叉积的作用。

代码在最后面!

Andrew 算法中维护栈(建议结合代码看)

维护下凸壳时,要求新的节点在 top 的前面的左边(new 1)。

那么就从 top - 1 连到 new,看看是否在向量 top - 1 连到 top 的左边。

这里直接计算两条向量的的叉积,看是否小于等于 0(右手定则),如果是就把 top 踢掉。

反之不踢,放到栈里。

维护上凸壳时,要求新的节点在 top 的后面的左边(new 2)。

那么就从 top - 1 连到 new,看看是否在向量 top - 1 连到 top 的左边。

这里直接计算两条向量的的叉积,看是否小于等于 0(右手定则),就把 top 踢掉。

反之不踢,放到栈里。

Rotating Calipers 旋转卡壳算法中比较距离(建议结合代码看)

如上图,指针 j 最远距离点的指针。

每次比较三角形 AFB 和三角形 AEB 的面积,因为俩三角形同底,所以面积大的那个高就大。

然后尝试用 AE 和 BE 更新直径。

(求三角形的面积用的也是叉积,比如说求 ABE 就是 \vec{AB}*\vec{AE} 叉乘出来的)

注释代码

求凸包时间复杂度是 O(Nlog\ N)(瓶颈是排序),旋转卡壳是 O(N)

#include<bits/stdc++.h>
using namespace std;const int N = 5e4 + 10;int n, top;   // P:输入点集, sta:凸包点集
struct Point {double x, y;
} P[N], sta[N];   // n:点数, top:凸包栈顶指针Point operator-(Point na, Point nb) {return {na.x - nb.x, na.y - nb.y};
}bool operator<(Point na, Point nb) {   // 从小到大排序:第一关键字 x,第二关键字 y if (na.x != nb.x) {return na.x < nb.x;}return na.y < nb.y;
}double operator*(Point na, Point nb) {   // 叉积 return na.x * nb.y - na.y * nb.x;
}double dis(Point a, Point b) {Point c = a - b;return c.x * c.x + c.y * c.y;
}void Andrew() {   // 安卓算法(apple? sort(P + 1, P + n + 1);top = 0;    // 清空栈// x 从左到右构建下凸壳,每一条边都要在前一条边的左边 for (int i = 1; i <= n; i ++) {// 维护栈的凸性:如果新点与栈顶两点构成非左转(右转或共线),弹出栈顶while (top > 1 && (sta[top] - sta[top - 1])* (P[i] - sta[top - 1]) <= 0) {top --;}top ++;sta[top] = P[i];} int t = top;    // 保存下凸壳终点位置// x 从右到左构建上凸壳,每一条边都要在前一条边的左边 for (int i = n - 1; i >= 1; i --) {// *注意!这里从 n - 1 开始遍历 // 因为求下凸壳时,最右点一定是 P[n],这里要拐回去往左边求上凸壳// 为了不重复,从 n - 1 开始 // 维护栈的凸性:如果新点与栈顶两点构成非左转(右转或共线),弹出栈顶while (top > t && (sta[top] - sta[top - 1])* (P[i] - sta[top - 1]) <= 0) {top --;}top ++;sta[top] = P[i];}  n = top - 1; // 更新 n 为凸包顶点数(首尾点重复,减 1)
}double RC() {    // 旋转卡壳算法求凸包直径(最远点对距离平方)double res = 0;for (int i = 1, j = 2; i <= n; i ++) {    // i:当前边起点, j:对踵点// 寻找 i -> i + 1 边的对踵点:比较相邻点到边的距离,用叉积计算出同底三角形面积后比较 // 当 j + 1 到边 i -> i + 1 的距离大于 j 到该边的距离时,j 向前移动while ((sta[i + 1] - sta[i]) * (sta[j] - sta[i]) < (sta[i + 1] - sta[i]) * (sta[j + 1] - sta[i])) {j = j % n + 1; // 循环处理(j 到达末尾时回到开头)}// 更新最远距离:比较当前边的两个端点到对踵点j的距离res = max(res, max(dis(sta[i], sta[j]), dis(sta[i + 1], sta[j])));}return res;
}int main () {ios::sync_with_stdio(false);cin.tie(0);cin >> n;for (int i = 1; i <= n; i ++) {cin >> P[i].x >> P[i].y;}Andrew();     // 构建凸包cout << fixed << setprecision(0) << RC() << "\n";return 0; 
} 

3.旋转卡壳应用

(AI 总结我修改的)

(1)凸包宽度问题

问题:计算凸多边形的最小宽度(平行切线间的最小距离)

解决方法

  • 宽度定义为两平行支撑线间的距离
  • 对每条边,找到距离该边最远的顶点
  • 用点到直线的距离公式计算
  • 维护最小宽度

(2)两个凸包间距离问题

最小距离

  • 两个凸包不相交时,最小距离在边界上
  • 旋转卡壳可以同时遍历两个凸包的边
  • 通过向量投影和叉积判断最近点对

最大距离

  • 类似单凸包直径,但需要在两个凸包间找最远点对
  • 维护两个指针,分别在两个凸包上移动

(3) 最小矩形覆盖问题 / 凸包内最大矩形

问题:找到面积最小的矩形覆盖所有点

解决方法

  • 最小面积矩形的一条边必然与凸包的一条边重合
  • 对凸包每条边:
    • 将该边作为矩形底边
    • 用旋转卡壳找到最高点、最左点、最右点
    • 计算对应矩形面积
  • 选择最小面积的矩形 优化:四个"卡尺"同时旋转,维护上、下、左、右边界

拓展

  • 在凸包顶点中找面积最大的四边形
  • 枚举凸包上的对角线,对于每条对角线,找到两侧使得四边形面积最大的两个点

(4)点集形状分析

  • 凸包面积 / 周长:基础应用
  • 形状特征提取:如偏心率、主轴方向
  • 碰撞检测:凸包间的快速相交判断
  • 模式识别:通过形状特征分类
http://www.dtcms.com/a/602092.html

相关文章:

  • 动作识别3——mmpose和mmaction2
  • 潍坊做网站优化网站设计项目
  • 2025企业可观测平台选型指南:聚焦核心能力,构建面向未来的观测体系
  • 郑州网站建设最低价鼓楼徐州网站开发
  • java开源Socket.io服务器端长链接通信解决方案
  • 牛客2025秋季算法编程训练联赛4-基础组
  • 视频类网站怎么做软件开发专业技能
  • 具身智能-一文详解视觉-语言-动作(VLA)大模型1.前言2.VLA的进化之路:从单兵作战到三位一体3.拆解VLA的大脑:核心组件全解析 预训练视觉表征
  • 空口协议栈的介绍及CA对其的影响
  • 网站建设制作优化推广类电商文案
  • 网站空间购买价格电子商务以后能干什么
  • C语言编译预处理 | 探索C语言编译过程中的预处理环节
  • kubernetes(k8s)-扩缩容(工作负载HPA、节点)
  • 广告设计网站官网北京小程序定制开发
  • 做网站怎样收费的怎样更换网站模板
  • 狸窝转换器将MP4格式视频转换为以下格式后的大小对比:RM、RMVB、AVI、MKV、WMV、VOB、MOV、FLV、ASF、DAT、3GP、MPG、MPEG
  • 【QT/C++】Qt样式设置之CSS知识(系统性概括)
  • 《华为应用市场编程工具上架深度拆解:鸿蒙适配与合规实战指南》
  • 29.删除链表的倒数第 N 个结点
  • 做黑帽需不需要搭建网站o2o的四种营销模式
  • 中英文的网站是怎么做的中国建设官网信息查询
  • 大航母网站建设流程黑龙江网站制作平台
  • C语言编译软件文档 | 如何高效使用C语言编译工具,提升编程效率
  • Android ROOM 数据库
  • 从红军城烽火到无人机时代:两场战争的跨越与现代军事启示
  • 营口网站seo网站开发的相关网站
  • 解决PyQt6安装失败:文件重命名权限错误与缓存清理方法
  • 第五章:MySQL DQL 进阶 —— 动态计算与分类(IF 与 CASE WHEN)多表查询
  • 【SQL server】不同平台相同数据库之间某个平台经常性死锁
  • Ubuntu系统安装.NET SDK 7.0