计算几何
向量
一般来说,解析几何用函数式表示图形。但在计算几何中,我们用向量表示。
一个具有长度、方向的线可以表示为以原点为起点,以点 \((x,y)\) 为终点的向量。数学表示为:\((x,y)=\mathbf{a}\) 或 \((x,y)=\vec a\)。
对于点 \(A,B\),可以用 \(\vec{AB}\) 表示以点 \(A\) 为起点,以 \((x,y)\) 为终点的向量。
向量运算
向量加减法
\[(x_1,y_1)+(x_2,y_2)=(x_1+x_2,y_1+y_2)
\]
\[-(x_1,y_1)=(-x_1,-y_1)
\]

向量乘法
\[(x_1,y_1)\oplus (x_2,y_2)=(x_1x_2,y_1y_2)
\]
它具有乘法的运算律,但一般没有实际几何意义。
数乘
\[k\cdot (x_0,y_0)=(kx_0,ky_0)
\]
满足乘法运算律

内积
\[\vec{(x_1,y_1)}\cdot \vec{(x_2,y_2)}=x_1x_2+y_1y_2
\]
向量不能连续内积,只满足交换律和分配律
他可以定义向量长度
向量长度
\[|\vec u|^2=\vec u\cdot \vec u\\
|\vec u|=\sqrt{\vec u\cdot \vec u}=\sqrt{x_1^2+y_1^2}
\]
内积的定义也可以表示为:(余弦定理形式)
\[\vec u\cdot \vec v=|\vec u|\cdot |\vec v|\cdot \cos \theta
\]
其中,\(\theta\) 为向量夹角(下同)
推导
我们先证明一个 极化恒等式:
\[\vec u \cdot \vec v = \frac{1}{2} \left( |\vec u|^2 + |\vec v|^2 - |\vec u - \vec v|^2 \right)
\]
证明:
\[\begin{aligned}
|\vec u - \vec v|^2 &= (\vec u - \vec v) \cdot (\vec u - \vec v) \\
&= \vec u \cdot \vec u - \vec u \cdot \vec v - \vec v \cdot \vec u + \vec v \cdot \vec v \\
&= |\vec u|^2 + |\vec v|^2 - 2 \vec u \cdot \vec v
\end{aligned}
\]
移项得:
\[2 \vec u \cdot \vec v = |\vec u|^2 + |\vec v|^2 - |\vec u - \vec v|^2
\]
所以:
\[\vec u \cdot \vec v = \frac{1}{2} \left( |\vec u|^2 + |\vec v|^2 - |\vec u - \vec v|^2 \right)
\]
考虑由向量 \(\vec u\)、\(\vec v\)、\(\vec u - \vec v\) 构成的三角形:
- 边 \(|\vec u|\):向量 \(\vec u\) 的长度
- 边 \(|\vec v|\):向量 \(\vec v\) 的长度
- 边 \(|\vec u - \vec v|\):从 \(\vec v\) 的终点到 \(\vec u\) 的终点的连线长度
\(\vec u\) 与 \(\vec v\) 的夹角为 \(\theta\),而 \(\vec u - \vec v\) 正是这个夹角所对的第三边。
在三角形中,由余弦定理:
\[|\vec u - \vec v|^2 = |\vec u|^2 + |\vec v|^2 - 2|\vec u||\vec v|\cos\theta
\]
移项得:
\[2|\vec u||\vec v|\cos\theta = |\vec u|^2 + |\vec v|^2 - |\vec u - \vec v|^2
\]
即:
\[|\vec u||\vec v|\cos\theta = \frac{1}{2} \left( |\vec u|^2 + |\vec v|^2 - |\vec u - \vec v|^2 \right)
\]
将第一步的极化恒等式与第三步的余弦定理结果对比:
\[\vec u \cdot \vec v = \frac{1}{2} \left( |\vec u|^2 + |\vec v|^2 - |\vec u - \vec v|^2 \right)
\]
\[|\vec u||\vec v|\cos\theta = \frac{1}{2} \left( |\vec u|^2 + |\vec v|^2 - |\vec u - \vec v|^2 \right)
\]
右边完全相同,因此:
\[\boxed{\vec u \cdot \vec v = |\vec u| \, |\vec v| \, \cos \theta}
\]
根据这个形式,我们可以推出
\[\cos \theta =\frac{\vec u\cdot\vec v}{|\vec u|\cdot|\vec v|}=\frac{x_1x_2+y_1y_2}{\sqrt{x_1^2+y_1^2}\sqrt{x_2^2+y_2^2}}
\]
由此,又能得到:
\[\begin{matrix}
\vec u\cdot\vec v>0 & x_1x_2+y_1y_2>0\\
\vec u\cdot\vec v=0 & x_1x_2+y_1y_2=0\\
\vec u\cdot\vec v<0 & x_1x_2+y_1y_2<0
\end{matrix}
\]
空间向量
我们也可以把向量扩展到高维,即定义 \(\vec u=(x,\;y,\;z)\)。此时,加减、内积、数乘结论仍能使用。
在三维空间里,我们可以额外定义一种向量运算:
外积
一般来说,两个向量可以表示一个平面,而外积就是直角坐标系内与这个平面垂直的向量。
\[\vec\omega=\vec u\times\vec v=\begin{vmatrix}
\vec{e_x} & \vec{e_y} & \vec{e_z}\\
x_1 & y_1 & z_1\\
x_2 & y_2 & z_2
\end{vmatrix}(其中,\vec{e_x}, \vec{e_y}, \vec{e_z})为x,y,z轴方向的向量\\=
(y_1z_2 - z_1y_2)\vec{e_x}
+ (z_1x_2 - x_1z_2)\vec{e_y}
+ (x_1y_2 - y_1x_2)\vec{e_z}\\
=
(y_1z_2 - z_1y_2,\; z_1x_2 - x_1z_2,\; x_1y_2 - y_1x_2)
\]
同时,\(\vec\omega\cdot\vec u=0,\ \vec\omega\cdot\vec v=0,\ |\vec\omega|=|\vec u|\cdot|\vec v|\)
在研究平面的计算几何中,我们可以把向量延伸出一维来使用外积运算。计算的结果为:
\[(x_1,\;y_1,\;0)\times(x_2,\;y_2,\;0)=(0,\;0,\;x_1y_2-x_2y_1)
\]
不过这样的话,它也不能连续计算了
一般我们认为,在计算几何中, \(|\vec u\times \vec v|=x_1y_2-x_2y_1\)。它的结果也是两个向量张成的平行四边形的面积
我们有:
\[\frac{|\vec u\times\vec v|}{|\vec u|\cdot|\vec v|}=\sin \theta
\]
所以我们可以用 \(\cos,\sin\) 值得到 \(\theta\)。
表示直线
用直线上一点 \(p\) 与直线方向向量 \(\vec u\) 表示直线 \(l\) 的点集为 \(\{\vec p+k\vec u\},k\in\mathbb{R}\).\
圆
我们用圆心 \(\vec c\) 和半径 \(r\) 来表示一个圆。
位置关系
\[\vec u\times(\vec a-\vec p)=0
\]
\[\vec u\times(\vec a-\vec p)>0
\]
\[\vec u\times(\vec a-\vec p)<0
\]
\[\vec {u_1}\times\vec {u_2}=0
\]
\[\vec {u_1}\cdot\vec{u_2}=0
\]
\[\vec p + \frac{\vec u_2 \times (\vec p_2 - \vec p)}{\vec u_2 \times \vec u_1} \cdot \vec u_1
\]
\[\frac{|(\vec a-\vec p)\times \vec u|}{|\vec u|}
\]
\[\vec p + \frac{(\vec a-\vec p)\cdot \vec u}{|\vec u|^2} \cdot \vec u
\]
\[|\vec a-\vec c|=r
\]
\[|\vec a-\vec c|<r
\]
\[|\vec a-\vec c| > r
\]
\[\frac{|(\vec c-\vec p)\times \vec u|}{|\vec u|} < r
\]
\[\frac{|(\vec c-\vec p)\times \vec u|}{|\vec u|} = r
\]
\[\frac{|(\vec c-\vec p)\times \vec u|}{|\vec u|} > r
\]
\[\text{设 } \vec h = \vec c - \vec p,\ d = \frac{\vec h \cdot \vec u}{|\vec u|},\ e = \sqrt{r^2 - \left(\frac{|\vec h \times \vec u|}{|\vec u|}\right)^2}\\
\text{交点 } = \vec p + \frac{d}{|\vec u|}\vec u \pm \frac{e}{|\vec u|}\vec u_{\perp}
\\\text{其中 $\vec u_{\perp} = (-u_y, u_x)$ 为 $\vec u$ 的垂直向量}
\]
\[|r_1-r_2| < |\vec c_1-\vec c_2| < r_1+r_2
\]
\[|\vec c_1-\vec c_2| = |r_1-r_2| \quad \text{(内切)}\\
|\vec c_1-\vec c_2| = r_1+r_2 \quad \text{(外切)}
\]
\[|\vec c_1-\vec c_2| > r_1+r_2
\]
\[|\vec c_1-\vec c_2| < |r_1-r_2|
\]
\[\text{设 } d = |\vec c_1-\vec c_2|,\ a = \frac{r_1^2 - r_2^2 + d^2}{2d}\\
\vec h = \vec c_1 + \frac{a}{d}(\vec c_2-\vec c_1)\\
\text{交点 } = \vec h \pm \frac{\sqrt{r_1^2 - a^2}}{d} \cdot \text{rot}_{90^\circ}(\vec c_2-\vec c_1)
\]
\[\frac 12|\vec{AB}\times\vec{AC}|
\]
\[(\vec a-\vec A)\times(\vec a-\vec B)=0 \quad \text{且} \quad (\vec a-\vec A)\cdot(\vec a-\vec B) \le 0
\]
\[(\vec{AB}\times(\vec C-\vec A)) \cdot (\vec{AB}\times(\vec D-\vec A)) < 0\\
(\vec{CD}\times(\vec A-\vec C)) \cdot (\vec{CD}\times(\vec B-\vec C)) < 0\\
\text{需同时满足}
\]
\[(\vec{AB}\times(\vec C-\vec A)) \cdot (\vec{AB}\times(\vec D-\vec A)) < 0\\
(\vec{CD}\times(\vec A-\vec C)) \cdot (\vec{CD}\times(\vec B-\vec C)) < 0
\]
向量旋转
我们可以用复数域来更好地解决向量旋转。
。。。
所以,\(\vec {(x,y)}\) 逆时针旋转 \(\theta\) 后的 \(\vec {(x',y')}\) 为
\[\vec{(x\cdot\cos \theta - y\cdot \sin\theta,x\cdot \sin\theta+y\cdot\cos\theta)}
\]
精度问题/代码问题
在计算几何内,我们不可避免地要遇到精度问题。这在计算机界十分知(致)名(命)。你当然可以用python,但如果不用呢?
首先,我们至少要用 long double,一般会用 __float128。但这也不好解决。
- 小数
- 如果我们要处理 \(\vec{(10^{18},10^{18})}\) 和 \(\vec{(10^{18},10^{18}+1)}\) 的向量运算,那么需要精度可能会到 \(10^{-18}\) 级别。
- \(\sin,\cos...\)
- c++实现这些操作依赖泰勒展开。虽然蒟蒻不会,但我们可以了解到泰勒展开有无限项,必然只会保留部分项。
- ...
再看上面,为什么我们要用外积表示左右?因为几种积运算都不容易出现精度大损失,没有除法问题不大。不过,精度误差会逐步累积,所以我们要尽量简化计算,或者是在整数范围内计算解决掉精问题。
这里我们也可以解释计算机为什么不涉及解析几何了——你总不能让计算机解方程还不用分数吧?
代码实现上,计算几何也极度难写。分类讨论极多,在精度问题下,以至于连判断相等都要小心。
平时刷题时,如果真遇见了计算几何,那就可以用deepseek提前写的封装类实现各种。场上也尽量用封装,便于调试。不过遇见的不多就是了。
凸包
凸包的定义很简单,就是平面上若干点,求一个最小的多边形包含所有点。想想橡皮筋套住钉子,那就是凸包。
对于每条边,多边形必然只在它一侧。利用这一点,我们可以实现查找凸包。
Andrew算法
我们知道,\(x\) 坐标最小和最大的点一定在凸包上。我们按上下侧,以这两个点为分界分为上凸壳和下凸壳。
我们使用单调栈,用 \(x\) 坐标排序,顺序枚举下凸壳,逆序枚举上凸壳。过程中,如果当前点在之前凸壳末尾的右侧,就把凸壳结尾删去。
实现时,判断左右侧用前面的向量知识维护,排序为防止同样 \(x\) 坐标,不要忘记按 \(y\) 坐标为第二权值。
Graham扫描法
和上一个算法基本一样,但按极角排序。容易发现思路本质相同。
比较
Andrew算法需要两遍,而Graham虽然只要一遍,但极角容易爆精,需要特殊方法排序。
旋转卡壳(旋转卡尺)
解决有关凸包信息的问题。
我们可以枚举凸包上每一条边,找到一条平行直线上一点,满足直线不经过凸包内部。这样我们可以维护凸包上关于对顶点对(就是刚刚枚举的,满足经过两点平行线不经过凸包)的信息。
- 直径:转的时候,维护距离。直径必定是对顶点对最大距离
- 最小矩形覆盖:在两个垂直方向上旋转卡壳,维护面积。
平面最近/远点对
距离:
- 欧式距离:\(\sqrt{(x_1-x_2)^2+(y_1-y_2)^2}\)
- 曼哈顿距离:\(|x_1-x_2|+|y_1-y_2|\)
- 切比雪夫距离:\(\max\left(|x_1-x_2|,|y_1-y_2|\right)\)
其中,切比雪夫距离和曼哈顿距离可以通过绕原点旋转 \(45\degree\) 来转换。
最近点对: 可以采用 人类智慧 分治法。
最远点对: 凸包直径。
半平面交
平面上,有很多半平面(直线一侧的面),求解它们的交。
多边形面积
凸多边形,我们可以把它剖成不同的三角形。而凹多边形,我们可以利用以下性质:
向量的叉积会根据不同方向而发生符号变化。利用此性质可以求凹多边形面积。
\[S=\left|\frac 12\sum_{i=1}^{n-1}a_i\times a_{i+1}\right|
\]