二维计算几何从入门到入门
前言
驻波在某场模拟赛被计算几何创死了,发现一点都不会计算几何。
恶补了一下简单的内容,不知道有没有用。
简介
接触了一些较为基础的计算几何知识。
本文重点讨论于二维平面内的计算几何。
部分题目来源于百练OJ,但是我都替换成 POJ 了。
语言表述可能并不是十分标准严格,见谅。
基本元素
点
一般用
向量
日常接触的是标量,即只有数值大小的量,而向量则是具有方向和大小的量(注意和位置无关)。
一般可以用二维平面内的一个点表示向量
向量有若干基础运算,对于两个向量
-
加:
\overrightarrow{a}+\overrightarrow{b}=(x_a+x_b,y_a+y_b) 。 -
减:
\overrightarrow{a}-\overrightarrow{b}=(x_a-x_b,y_a-y_b) 。 -
数乘:
k\overrightarrow{a}=(kx_a,ky_a) 。 -
数除:
\frac{\overrightarrow{a}}{k}=(\frac{x_a}{k},\frac{y_a}{k}) 。
在坐标系中,对于
向量一般用点来表示。
点乘
基础定义不多阐述,一个特变的地方在于点乘的结果正负和两个向量的夹角有关。
无精度误差:
实际含义为第二个向量在第一个向量的投影长度与第一个向量模长之积。
叉乘
基础定义不多阐述,一个特别的地方在于差乘结果正负和两个向量的相对关系有关。
无精度误差:
如果为正,说明第二个向量在第一个向量逆时针方向,如果为负,说明在顺时针,为零则贡献。
实际含义为两个向量形成的平行四边形面积,注意是可负的,正负形可以用右手定则判断。
直线
两点确定一条直线。
判断点线关系:用叉积,直线上任选两个点构造共起点向量算叉积即可判断向量位置关系,其实正负号就反应点线关系了。
判断直线关系:在直线
-
判断共线:
\overrightarrow{AB},\overrightarrow{CD} 的叉积为零。 -
判断重合:贡献基础上,
\overrightarrow{AB},\overrightarrow{BC} 的叉积为零。
找交点坐标:如果不共线,则:
两边叉乘
点到直线距离:平行四边形面积为底成高,已知底(两点长)和面积(叉积算),直接求高。
线段
判断点在线段上:一种方法是先判断在直线上,然后在看
判断线段相交:好像叫跨立试验,具体的,线段
多边型
面积:三角剖分,三角形面积是平行四边形一半,用叉积。
点和多边形关系:
-
射线法:往左射一条射线,交点奇偶性表示在内在外。
-
转角法:把点和多边形顶点连接起来,累加相邻两个顶点之间的夹角,为
0 则在多边形内部。
特殊的,如果判断在一个凸包内:
-
给凸包所有边定同一方向,如果最后点在所有边的同一方向,则在内部。
-
选择一个点,然后极角排序,三角剖分,二分即可(适合多次查询点和多边形关系)。
题目 1
入门难度的题。
POJ1269
直接判断即可。
POJ2318
注意到线段不想交,可以二分最后一个在点左侧的线段,时间复杂度为
P1335
先特判掉顶点和边上的情况。
如果在三角形内部,按照逆时针或者顺时针方向给三角形的边变成有向的线段,如果这个点相对于三个向量的关系一样,则在三角形内部。
POJ1039
线一定可以经过两个端点,不然可以旋转调整到对应状态。
枚举两个端点,得到穿过的射线,然后直接能穿过几段管道,算交点即可。
基础算法
极角排序
其实就是选择一个点建立极坐标系,然后让其他点按照极角排序。
其实极角排序可以通过角度范围映射到数轴上的范围,解决一些别的题目。
方法一:用 C++ 自带的 atan2 实现。
方法二:同一象限的点用叉积判断关系,不同象限的则比较象限。
题目 2
极角排序的简单题。
P14428
对于两个不重叠的三角形,一定可以通过一条直线将整个坐标系分开成两个平面。
通过旋转,可以让直线严格经过两个端点。所以枚举两个端点,得到一条直线。
计算直线两侧能得到的合法三角形,乘起来即可。
具体是选择一个基准点,然后极角排序,枚举另一个直线端点,双指针维护半个平面里各个颜色点的数量。
复杂度为
P1691
可以证明最优解一定经过两个端点。枚举一个基准点。
先极角排序,然后一条线段两个端点对应的极角,得到一个范围,另外一个端点选择的极角在这个范围就会产生一些贡献。
问题变成一些区间,区间有权值,选择一个点权值最大,随便做就行。
复杂度为
P3699
枚举一个基准点,然后计算这个基准点能向外连出的线。
一个三角形会让一个极角范围内的点可能被阻挠。把这个三角形拆成两次操作,一次加入一次删除做扫描线。
需要求出某个极角时距离基准点最近的三角形,实际上,两个三角形加入后,因为不相交,他们任意时刻到基准点的距离远近相对关系和刚加入的关系一样,直到某一个被删除。
所以问题变成单点修改全局最大,用 set 或者可删堆即可,复杂度为
凸包
给定平面内的
凸包由上凸壳和下凸壳构成,特点是边的斜率递增或递减,在优化 dp 或者数据结构中有用途。
Andrew 算法
求凸壳的算法。
先找到以
先构建下凸壳,维护一个栈表示当前凸包边上的点,每加入一个点就判断栈顶三个点是否满足向内收的趋势(具体特征是相邻两点向量的位置关系是否呈逆时针)。用叉积可以快速判断。
然后同理构建上凸壳(这个顺序无所谓),时间复杂度为
闵可夫斯基和(凸包)
凸包其实可以看成一个点集,对于两个凸包
具体求法是先拆凸包的边,然后按照斜率去归并排序,最后就能合并出新的凸包,时间复杂度为
在一些特殊条件下,序列的
习题 3
凸包的简单题。
P2742
凸包模板题。
P3829
先求出每个圆的圆心,整个凸包的形状大概就是圆心的凸包的形状。
先忽略圆弧的边,剩下的边通过平移可以发现和原来凸包的周长是一样的。
因为是一个凸多边形,所有圆弧所对圆心角之和为
直接算就行。
P2116
注意到凹下去的部分一定不优,不如不凹。
所以转化成凸包距离为定值的图形周长,和上面就是一样的。
P4557
若存在
把
找出基准点后极角排序然后三角剖分,可以快速查询,复杂度为
POJ1696
每层剥掉最外层的凸包点。从最外层任意选一个点断开,然后往里面螺旋一定有合法解。
旋转卡壳
前面忘了,但是十六种读法。
可以用来解决一些最远点对的题目。在一个凸包上,维护两根平行线,把整个凸包夹住。
当一个平行线和边重合时,另外一个平行线可能会交一个点或者一个边,这个时候用两部分算贡献即可。
习题 4
旋转卡壳简单题目。
P1452
模版题。
维护另外一个平行线交的点时可以用叉积求和当前平行线交的边最大面积的点,因为底一定,只和高有关,而凸包的结构决定高是个单峰函数。
直接双指针即可,复杂度为
P3187
在上面基础上,同时维护一个最右和最左点即可,判断方法用点积(本质是投影长度乘积)。
先求出一交点,然后顺时针求出另外四个点即可。
注意最左最右的投影长度可能是多峰。复杂度为
P4357
每次求出最远点对
对于第
其实这个题可以做到 nth_element 随便做一下。
POJ3851
减一下,然后得到差向量的集合。
其实是个凸包合并,用闵可夫斯基和做一些就行。
当然也有旋转卡壳做法,需要维护有向线段的最长最短。
半平面交
定义满足
半平面交就是给定若干
半平面交最终形态在一般情况下会类似一个凸包(当然有很多特判的情况,非常复杂,不过大部分题目会简化掉一些情况)
一个求出半平面交的算法是增量法,让所有半平面按照极角排序,维护一个不完整的凸壳,内部就是当前的半平面的交,然后依次加入新的半平面。
具体的,维护一个双端队列,表示对当前半平面交有约束的半平面。此时要新加入一个半平面。
如果队列维护的凸壳的最后一个点不在当前加入的半平面,弹出队尾元素。
当半平面交形成一个凸包时,凸壳的第一个点也可能不在半平面内,需要弹出队首元素。。
因为队首元素在不断变化,所以最后需要用队首更新队尾多余的部分。
有时,半平面交不一定是封闭的,但是题目会给定边界,可以手动加入边界。
这种算法只能处理大部分的半平面交情况,复杂度为
题目 5
半平面交的板子题。
P4196
真板子,把凸包的边拆成半平面跑即可。
P3256
去重,用单调栈处理掉绝对不可能成为第一的车,剩下的按照初始位置排序。
建模:初始位置当做截距,速度当做斜率,得到一条直线,求出所有直线上半部分的半平面交,则只有存在在最终半平面交上的直线才有可能成为第一。
最终答案一定是下凸壳,可以简化半平面交的很多步骤。
P2600
把下面的多边形拆边,能看到这条边的范围是一条边。
求出半平面交,即为能看到所有边的位置集合。
最小距离一定在顶点所在的
P4250
可以感受到合法的站位一定是一个区域。可以求出这个区域比整个多边形的面积。
实际上,枚举
求出半平面交即可,可以用三角函数算一下夹角,也能直接用向量推。
P3297
考虑什么时候答案会增加,就是当从一个人的监视区域移动到另一个人的监视区域。
每个人监视区域其实可以通过其他人对他的约束,最终是一个半平面交。
可以发现只可能从两个边相交的半平面之间移动,连边然后 bfs 即可,复杂度为
POJ3464/P3799
建模后会发现都是要求多边形内是否存在一个点能看到其他店,其实等价于 P2600。
P5328
首先排名为
考虑排名为
具体的,可以先求出来一个半平面交,然后将当前半平面交上这个选手位于第一名的
然后将上面的点删掉,然后再求半平面,此时第一个点能成为第二必须满足:
-
他此时在半平面交上。
-
他在平面交上的支配范围内,存在一个
x 使得这个位置只有一个标记。
然后依次类推做就行了。
复杂度大概是
后记
其实看了才会发现,大部分题目都是难度虚高。主要难度在于应对大粪般的特判。
参考资料:OI-wiki,算法竞赛,其他文章。