计算几何 Geometry 知识Preview

本文章不追求彻底搞懂计算几何,只追求先把计算几何最基础的知识点和工具大致过一遍,建好知识体系与知识框架

向量 Vector

外积 Product

仅在三维向量空间中适用,记为 a×ba\times b,其定义如下:

  1. a×b=absin<a,b>|a\times b|=|a||b|\sin\left<a,b\right>
  2. a×ba\times ba,ba,b 均垂直,满足右手法则

接下来我们对于二维向量讨论:

坐标运算

a×b=x1y2x2y1a\times b= x_1y_2-x_2y_1

正负号 = 旋转方向

对于 a×ba\times b

image-20260702154431995

线性变换

对向量进行变换,相当于乘上一个矩阵

放缩变换

构造对角矩阵:

image-20260702144327414

二维旋转变换

方法:先把原向量的 x,yx,y 分解,然后分别投影到选择后的新坐标轴上,再求出新坐标

通用公式:

image-20260702144452886

平移变换

对于二维向量,因为增加常数 tt 非线性,所以我们引入齐次坐标,变成三维,最后一列是平移量。

二维计算几何 2d

极坐标 Coordinate

极坐标是以一点为中心,以 (x,α)(x,\alpha) 记录位置的坐标表示方法。其中 α\alpha 代表旋转的弧度。

直线的表示

在计算几何中,表示直线一般使用直线上的一个点+方向向量来表示。

判断线段是否有交点

对于线段 ABABCDCD,若二者有交点,必然满足:

  • C,DC,D 在直线 ABAB 的异侧
  • A,BA,B 在直线 CDCD 的异侧

判断是否在异侧有向量的叉积即可

线段交点求解

先判断一下平行的情况

取点 B,CB,C。我们要求 AA,只需要知道 ll 的长度,然后用 BB 平移过去即可。

显然 α,β,s\alpha,\beta,s 都是好求的,然后上正弦定理即可。

image-20260702145937866

多边形面积

多边形的存储我们一般用逆时针的顺序来存点。

考虑向量外积的几何意义,我们只需要求每条相邻边外积和的绝对值即可。

三维计算几何 3d

平面的表示

我们一般使用平面上的一个点 AA 和平面的法向量 n\vec{n} 来表示一个平面。

那么平面上的所有点 BB 均满足:

  • ABn=0\vec{AB}\cdot \vec{n} = 0

且充要。

三余弦定理

我们有 cosBOC=cosBOAcosAOC\cos\angle BOC=\cos\angle BOA\cdot\cos \angle AOC

image-20260702150840863

三正弦定理

我们有 sinγ=sinαsinβ\sin \gamma=\sin\alpha\cdot\sin \beta

image-20260702151432363

Pick 定理

  • 适用:网格图、顶点均为整点

定义:对于简单多边形,其面积 AA,内部格点数 ii,边上格点数目 bb 满足:

A=i+b21A=i+\frac{b}{2}-1

判断边上的格点数目的方法:

因为线段长度均为整点,设一条线段两点差为 (dx,dy)(dx,dy),则线段上点的数量为 gcd(dx,dy)+1\gcd(dx,dy)+1。但注意,两端点会被计算两次。

三角剖分 Triangulation

我们设法求完美三角剖分,我们使用 Delaunay 三角剖分 法。

这种剖分有两个性质:

  1. 任意一个三角剖分的外接圆不含其它任何的点
  2. 在所有剖分中,这种剖分的最小角最大

分治构造法

我们先把所有点按 xx 升序排列,然后不断分治,直到子点集大小不超过3。

我们现在开始回溯,考虑把所有左右两部分的三角剖分合并。

合并的操作:

  • 第一步:插入 base L-R edge,即最底下一条不与左右任何边交叉的边。
  • 第二步:左右各自筛选可能的点

image-20260702152708437

可能的点满足

  1. 是 LR-edge 中其中一个点原来的连边(这个是3中可能点的定义)
  2. 与 LR-edge 的夹角小于180度
  3. 这三点构成的圆内不包含任何的可能点

image-20260702152917449

图中的7号点满足条件。于是我们连接 2-7 为我们下一条 L-R edge,并删除 6-8 点。

以此进行下去。当左右均不存在可行点时,我们就完成了合并。

Voronoi 图

Delaunay三角剖分和Voronoi图是对偶关系。

定义:把平面切成一块块多边形,这个多边形里所有点,到内部种子点的距离,比到别的点的种子点的距离近。

转化:对种子点进行Delaunay三角剖分后,连接每个三角形取外接圆的圆心连在一起即为Voronoi图。

凸包

包含所有点的最小凸多边形(用橡皮筋捆钉子理解)

  • 性质:任意相邻两条边的叉积必然大于0(左拐)(我们点是逆时针存放)

Andrew 单调链法

  1. 排序:把所有点按 xx 坐标从小到大排序
  2. 建下凸壳:从左往右扫,用栈维护,只要新加入的点和栈顶的两个点发生右拐,就弹出当前栈顶的点
  3. 建上凸壳:从右往左扫,过程如上
  4. 合并上下凸壳

复杂度 O(nlogn)O(n\log n)

Craham 扫描法

先找出纵坐标最小的一个点 PP,然后按到这个点的极角大小来排序。

然后维护一个栈,过程和Andrew法同理。

闵可夫斯基和

AA 中每个点加上 BB 中每个点的坐标得到的图形就是闵可夫斯基和。

image-20260702155223294

而且这样合并完后也是一个凸包。

重要性质:对于原凸包 P,QP,Q,闵可夫斯基和合并后的凸包 P+QP+Q 的边集是 P,QP,Q 中每条边按极角排序后的边集

P,QP,Q 中的边本身已经按极角排好序,我们只需要 O(n+m)O(n+m) 的时间即可求出闵可夫斯基和。起点为 P1+Q1P_1+Q_1

扫描线

要求:离线

方法:

  1. 离散化
  2. 把横着的边按纵坐标排序
  3. 线段树维护

二维数点问题

长为 nn 序列,mm 次询问,每次询问在 [l,r][l,r] 内值域为 [x,y][x,y] 的数的个数

对询问离散化,从左往右扫序列,树状数组维护值域。l1l-1 时统计一下,rr 时再统计一下。

旋转卡壳 Rotating Calipers

通过两条平行的线,整体绕凸包逆时旋转,同时维护卡尺顶点,在线性时间内求解凸包极值问题。

求凸包直径

凸包中每条边对应一组平行卡尺,一条边贴着当前边,另一条平行线在离该边最远的顶点,且必然逆时针旋转。

于是我们每次移动边的时候不断判断下一个点是否离该边更远。

判断的过程可以用叉积计算三角形面积来。

image-20260702161455192

最小面积矩形覆盖

最优的矩形必然其中一条边贴合,另外三条边经过凸包上的点。

因为我们维护三个指针即可,而且这三个指针相互独立。

求对面点的方法同理。求两侧点可以使用点积,因为点积的本质是投影长度。

如图中所示,我们可以通过用点积求 m,nm,n 进行判断。

image-20260702161551234

半平面交

  1. 半平面:一个直线一侧的所有区域(由于我们直线是用向量定义,所以可以统一约定直线的左侧或右侧)
  2. 半平面交:多个半平面的交集
  3. 多边形的核:多边形内部的一篇区域,此区域内任意一点可以看见多边形的所有边界

多边形的核求法:把多边形每条边视为向内的有向直线,所有半平面的交集就是核

S&1 算法

  • 第一步:极角排序(同极角只保留最内侧)
  • 第二步:单调队列维护线段

哪些边会被pop掉?队尾的边 + 队首的边(在最后封闭的时候会被pop)

  • pop判断标准:上一个交点在当前这条向量表示的半平面的异侧

如图所示,插入 c\vec{c} 边,D点在 c\vec{c} 右侧,故 b\vec{b} 被pop掉。

队首也会被pop,如图,加入 f\vec{f},则 a\vec{a} 会被pop掉。

image-20260702164612887

注意先pop队尾再pop队首。

平面最近点对

求二维平面最近的点对(欧氏距离)

分治法

xx 排序,随后分治。

考虑左侧最小为 h1h_1,右侧最小为 h2h_2,我们令 h=min(h1,h2)h=\min(h_1,h_2)

此时我们只需要考虑里中线差值小于 hh 的点。我们对这些点对 yy 排序(这个过程两边可以本身就按 yy 排好,然后two-pointer合并即可)

此时对于每个点,我们只和它下方、纵向差小于 hh 的点计算距离。

关键性质:这个点最多只和下方7个点比对

感性证明:若超过,则之前的答案必然小于 hh

复杂度 O(nlogn)O(n\log n)

有序扫描 + 平衡树

这里的平衡树我们可以用 multiset 代替。

  1. xx 排序
  2. 建立平衡树,按 yy 坐标来维护
  3. 对于每个点,假设之前的答案为 dd
    1. 先删除横向距离 Δxd\Delta x\ge d 的点
    2. 在平衡树中逐个遍历 [yd,y+d][y-d,y+d] 的点
    3. 将当前点插入平衡树

同样可以由每个点仅少量候选实现常数保证。

复杂度 O(nlogn)O(n\log n)

随机网格哈希法

  1. 随机打乱所有点
  2. 假设前 i1i-1 个点的答案为 ss,我们按 ss 为边长划分网格
  3. 对于当前第 ii 个点,我们只检查自身和周围9个网格的点。这些点的数量受到 ss 的限制,是 O(1)O(1) 个的。
  4. ss 被更新,我们就重构网格。由于我们已随机打乱,所以重构次数均摊下来仍为 O(n)O(n)

复杂度期望为 O(n)O(n)

随机增量法

我们先对输入数据随机,然后每次增加一个来更新答案。

最小圆覆盖

平面上 nn 个点,求最小的圆覆盖所有点。

我们先把所有点打乱:

第一层:我们遍历 ii

假设我们已有前 i1i-1 个点组成的最小圆,如果 ii 在当前的圆里,跳过。

否则说明 ii 一定在最小圆中。我们把 ii 作为当前圆一点,半径设为0,并进入第二层。

第二层:我们在 1i11\sim i-1 中遍历 jj

jj 点在前 j1j-1 构成的最小圆中,跳过。

否则建立以 i,ji,j 为直径的圆,进入第三层。

如果 jj 全部遍历完,回到第一层。

第三层:我们在 1j11\sim j-1 中遍历 kk

kk 点在前 k1k-1 构成的最小圆中,跳过。

否则建立以 i,j,ki,j,k 为直径的圆。

如果 kk 全部遍历完,回到第二层。

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
函数 求最小覆盖圆(所有点):
shuffle(所有点) // 第一步:先把点随机打乱顺序
当前圆 = circle_1点(所有点[0]) // 初始圆:只有第一个点

// 第一层:逐个往里加第 i 个点
遍历 i 从 1 到 最后一个点:
第i个点 = 所有点[i]
如果 is_inside(第i个点, 当前圆) 为真:
跳过 // 点本来就在圆里,圈子不用改
否则:
// 点在圆外 → 新圆必须经过这个点,重置圆
当前圆 = circle_1点(第i个点)

// 第二层:找第二个必须贴在圆边上的点 j
遍历 j 从 0 到 i-1:
第j个点 = 所有点[j]
如果 is_inside(第j个点, 当前圆) 为真:
跳过
否则:
// j也在圆外 → 圆必须同时过 i 和 j
当前圆 = circle_2点(第i个点, 第j个点)

// 第三层:找第三个必须贴在圆边上的点 k
遍历 k 从 0 到 j-1:
第k个点 = 所有点[k]
如果 is_inside(第k个点, 当前圆) 为真:
跳过
否则:
// k也在圆外 → 三点定一个圆
当前圆 = circle_3点(第i个点, 第j个点, 第k个点)

返回 当前圆

原理:三点定圆。所以最多循环3次即可。

由于我们已随机排序,所以期望复杂度为 O(n)O(n)

反演变换

由于反演后的相切关系不变,还可以把圆变成直线,所以常用于处理复杂多圆相切问题。

image-20260702170718053

OO 为我们的反演中心,RR 为我们的反演半径(随便取)

定义:

  • OPOP'OPOP 的射线上
  • OP×OP=R2|OP|\times |OP'|=R^2

PPPP' 必然在圆的内外侧(如果在圆上,则 PPPP' 重合)

性质:

  • 1 不经过反演中心的圆,反应后还是不经过反演中心的圆

image-20260702171057686

  • 2 经过反演中心的圆,反演后是不经过 OO 的直线

image-20260702171143605

因为圆过 OO,所以必然有一个反演点无限会跑到无穷远。

同理,我们可以把切线反演回圆。(因此如果我们想求两圆公切圆,可以先先把两圆反演求公切线,再反演回去)

  • 3 两图形切点不是 O,则他们的反演图形仍然相切