试着用计算机来解决几何问题吧(●’◡’●)
前置知识
- 几何基础
- 平面直角坐标系
- 极坐标与极坐标系
- 向量(向量积)
二维计算几何基础
图形的表示
点与线的表示
例图:
|
|
距离与旋转
例图:
|
|
向量旋转解析
设$\vec{a}=(x,y)$,倾角为$\theta$,长度为$l=\sqrt{x^2+y^2}$。则$x=l\cos{\theta},y=l\sin{\theta}$,顺时针旋转后得到$\vec{b}=(l\cos{\theta+\alpha},l\sin{\theta+\alpha})$。
![image-20240312200008353](https://cdn.jsdelivr.net/gh/Florae006/dodolaPicBed/image-20240312200008353.png)
三角恒等变化: $$ \vec{b}=(\cos{(\theta+\alpha)}l,\sin{(\theta+\alpha)}l) $$
$$ =(l(\cos{\theta}\cos{\alpha-sin{\theta}sin{\alpha}}),l(\sin{\theta}\cos{\alpha}+\sin{\alpha}\cos{\theta})) $$
$$ =(x \cos{\alpha-y \sin{\alpha},y \cos{\alpha}+x \sin{\alpha}}) $$
点积与叉积
例图:
![image-20240312200022870](https://cdn.jsdelivr.net/gh/Florae006/dodolaPicBed/image-20240312200022870.png)
|
|
- 点积:也叫数量积、内积。几何意义是一个向量在另一个向量上的投影再乘第二个向量的模长,是一个实数,有交换律。
- 若同向($\theta = 0°$),为模长之积。
- 若锐角($\theta \lt 90°$),数量积为正
- 若直角($\theta = 90°$),数量积为0
- 若钝角($\theta \gt 90°$),数量积为负
- 若反向($\theta = 180°$),为模长之积的相反数
- 叉积:也叫外积,几何意义是两向量围成的平面四边形的面积。是一个向量,其方向$\vec{a}\times \vec{b}$:用右手从$\vec{a}$沿着不大于平角的方向向$\vec{b}$旋转,拇指方向是外积的方向。没有交换律。
- 若平行,外积为$\vec{0}$
- 共起点后,对于$\vec{a}\times \vec{b}$,若$\vec{a}$在$\vec{b}$的右侧,外积为正,否则外积为负(以纸面为参考,$\vec{a}$往$\vec{b}$是逆时针为正,顺时针为负)
由向量外积可以判断两向量的旋转关系、方便求出点到直线的距离。适用于凸包和旋转卡壳。
观察两个式子:
-
投影:$dot=|A||B|\cosθ$
-
面积:$det=|A||B|\sinθ$
可以发现点积与叉积的正负由角度决定,故根据点积和叉积的正负,可以判断向量夹角的象限。
|
|
判等判交求交
浮点数等于零
注意浮点数的精度误差,一般用sgn()
来实现相等判断
|
|
点判等
也是一样要注意精度捏
|
|
点在线段上
点在线段上有两个要求:
- 点在直线上:叉积判断共线
- 点在两端点上:点积判断方向
|
|
- 叉乘为0则共线(0°或180°)
注意:可能需要处理线段退化的情况:线段退化时直接调用会返回true
。
线段退化情况在调用之前自行判断。
线段判交
线段有交的两种情况:
- 一条线段的端点在另一条线段上。
- 互相严格跨立。
第一种使用point_on_segment()
处理;
第二种用叉积判断角度异号。
|
|
几种测试情况思考:
![]() |
![]() |
---|---|
![]() |
![]() |
直线求交
求两条直线的交点
直线和直线的位置关系有三种:
- 平行
- 有一个交点
- 重合(共线)
判断位置关系
思路:
|
|
判断平行(包括重合):叉积是否为0
|
|
判断共线:平行且交换端点后也平行
|
|
两直线相交求交点
使用面积计算等高三角形底边的比例求交点。
注意:这里使用了除法,除法会导致精度严重下降(通常epsilon就是为了克服除法的误差而引入的)
这里$△ABC$与$△ABD$共底,面积之比(叉积之比)即为CK与KD的长度之比。则:
对$K(k_x,k_y),C(c_x,c_y),D(d_x,d_y)$,有: $$ CK=\frac{u}{u+v}CD $$ so:K是CD的一个定比分点。
|
|
射线求交(留思考
两条射线之间的位置关系:
- 平行(不重合)
- 共线同向(部分重合或完全重合)
- 共线反向且不重合
- 共线反向且部分重合
- 相交
求交点
两条射线如果有相交交点的话(情况5),这个交点一定是两条射线所在直线的交点,用前文的方法求即可。
判断相交
对于射线来说,若有交点,则交点一定在两条射线所在直线上,射线可以看作其所在直线的一部分,那么只要判断这个交点是否同时在两条射线的延长线方向即可。
距离
点到线段距离
点到线短距离有两种情况:
- 垂线段长度
- 到某个端点的距离
- 注意线段退化问题
|
|
凸包
能包含所有给定点的最小凸多边形的叫做凸包
- 凸多边形:每个内角在$[0,\pi)$内的简单多边形;如果允许非严格则是$[0,\pi]$
- 或者,点集所有可能的带权平均点集合为凸包。
求凸包
有两种求法:Graham算法和Andrew算法,两种算法的时间复杂度都是$O(n\log n)$。区别在于对点的排序方式不同。
Andrew算法
对点的排序方法:按照$x$为第一关键字,$y$为第二关键字,对点集进行排序,排序完成后,易知:第1个点和第n个点一定在凸包点集里。
可以分别求出下凸壳和上凸壳,求上下两半时也不会互相影响。
算法:用栈来维护在凸包上的点
- 逆时针先求下凸壳:第1个点和第2个先入栈,当加入更多点时,设栈中的倒数第二个点为$A$,最后一个点为$B$,新加入的点为$C$,若$B$在$\vec{AC}$的左边(或在线上),则将$B$弹出,令$C$入栈。
- 接下来依然是逆时针求上凸壳,上述求下凸包时,最后入栈的点一定是$n$号点,在加入$n-1$号点后,我们继续上一步的一模一样的操作:判断旧点和新向量的位置关系再做取舍。
- 注意,求上凸壳的时候依然是遍历到1号点,此时会有首尾相同,点重复的问题。
模版代码:
|
|
凸包的周长:($k$为凸包中点的个数) $$ C=\sum_{i=0}^{i=k-1}dis(p_{i},p_{(i+1) \mod{k}}) $$
|
|
Graham算法
取左下角的点为基准,对其余点进行逆时针排序。
- 由于其他点相对于基准点在$[0,\pi )$的半平面内,因此可以直接使用叉积排序
- 不要使用
atan2
:当值域很大时,精度难以区分相近的点 - 注意处理极角序相同的点:按照到基准点的距离从小到大排序。
用一根细线尝试绕过所有点。
- 需要弹出不满足凸性的点
- 使用单调栈实现
|
|
两种算法相比
Graham不够好:相对较慢,容易写错
- 相对较慢:排序需要计算$O(n\log n)$次叉积。
- 任意写错:细节比较多,任意出现挂边界的情况。
Andrew算法扫描两遍计算出上下凸壳,通常在效率和实现上相比Graham有优势。
凸包典题:
[P2742 USACO5.1] 圈奶牛Fencing the Cows /【模板】二维凸包 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
[P3829 SHOI2012] 信用卡凸包 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
三分
二分要求单调性,最基本的应用是求一个单调函数的零点。
三分是二分的变种,它的基本用途是求单峰函数的极值点。
三分的原理:以求极大值为例。每次对一个区间$[l,r]$求三等分点$lp$和$rp$:
- 如果$f(lp)\lt f(rp)$,说明极大值一定在$[lp,r]$内取到,因为如果在$[0,lp]$内,那$rp$一定处于单调下降的区间内,它的函数值不可能大于$f(lp)$,于是我们令$l=lp$
- 如果$f(lp)\gt f(rp)$,同理,极大值一定在$[l,rp]$内取到,令$r=rp$
这样进行下去,直到$fabs(l-r)\lt eps$为止,如果是求极小值,只需要把处于判断处的大于小于互换。
|
|
优化
按照上面的算法,每次减少三分之一的长度,但其实还可以通过在中点附近取点来优化,这样每次可以减少约二分之一的长度。
|
|
题单部分
基础
-
P1355 神秘大三角 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) // 这题莫名卡输入格式,坏!
一些子问题Q
- 如何求一组点中用一条直线穿过的最多的点数?$O(n^3)$做法、$O(n^2logm)$做法
难一些的
模版代码
|
|