啊,计算几何好难啊。
估计今天要爆炸了,写一堆笔记。
虽然计算几何在OI里应该考得不多,不过还是要看一看0.0
首先是一些注意事项。
对于scanf/printf来说,long double 类型不能直接读入,要用%lf读入double,然后再将double的变量赋值给long double的变量。输出也不能直接输出(windows下是不能的)ldb实际情况下是为了减少运算过程中的误差。
实数判断大小使用手写sign函数。
一般来说cmath里有很多函数可以用的,适用double,如果是ldb要改一下。
比如fabs(x) ——> fabsl(x)
另外玄学:ldb比db快??虽然内存会大??一般用double就行了。
其实计算几何都是一堆一堆的公式,那当然是贴公式了。
算了没什么好贴的,都是靠平时的积累lo。
写一些在C++里有用的。我们有:
叉积:
对于向量 a/b在同一起点 a / b 在 同 一 起 点 ,有叉积 s=a×b s = a × b ,若 s>0 s > 0 ,则 b b 在 a a 的左边。若 s<0 s < 0 ,则 a a 在 b b 的右边。
若 s=0 s = 0 , a和b a 和 b 同向或反向。
表示一条直线:
1.解析法: y=kx+b y = k x + b
这里的k可能到INF,怎么处理呢?我们可以将所有点旋转一个比较小的度数,然后做完再转回去。(比如旋转 1.927∘0.817∘ 1.927 ∘ 0.817 ∘ ,rqy说要转 sin s i n 在 1e−6 1 e − 6 左右的,那我们就转 1019260817 10 19260817 ,或 8.17e−5rad 8.17 e − 5 r a d )
2.双点法:用两个点表示一条直线
3.点向法:用一个点加一个向量表示一条直线
向量法转点向法是向量 (1,k) ( 1 , k ) 点 (0,b) ( 0 , b )
推荐用第三种。
向量 (x,y) ( x , y ) ,逆时针转 90∘ 90 ∘ 后的向量是 (−y,x) ( − y , x ) ,顺时针转 90∘ 90 ∘ 就是这个东西写三次,也就是 (y,−x) ( y , − x ) 。
点与直线的关系
点向法判断点是不是在直线上,直接算这个点和记的点组成的向量,与记的向量的叉积是不是0(是则在)即可。
点与射线的关系
点向法判断在叉积的基础上还要看两个向量的点积是否大于等于0(是则在)。
点与线段的关系
点向法判判断在前两个的基础上,我们将记录的向量看作两条射线,那么如果新的点同时在两条两条射线上即是在线段上。
可以在直线的基础上,计算这个点到线段两端的距离是否都小于线段长。
也可以直接计算这个点到线段两端的距离和,当距离和等于线段长时在,但可能精度误差较大。
还可以在直线的基础上,判断是否有: x1≤x≤x2并且y1≤y≤y2 x 1 ≤ x ≤ x 2 并 且 y 1 ≤ y ≤ y 2 ,这个应该是最简单的了。
点到直线的距离
显然是用 |a×b||a| | a × b | | a |
直线与直线
两条直线的交点?
解析法我们都会,那么如何用点向法呢?用定点分解的方法。
设两个“点向”为 p1,v1 p 1 , v 1 以及 p2,v2 p 2 , v 2
那么设 u=向量(p1,p2) u = 向 量 ( p 1 , p 2 )
然后交点就是:
两条线段的交点呢?
我们要先判断两个线段有没有相交(此处端点相交不算)。
设两个线段为 AB A B 和 CD C D ,那么我们会发现,对于 AB A B , CD C D 的两个端点分别在 AB A B 的左右两侧,对于 CD C D 同理,所以我们要同时满足:
当然这种方法可能时间复杂度较高(EXM?)
其实我们可以算出直线交点后判断这个交点是否在两条线段上。这个东西前面有。
以下有 △ABC △ A B C
中线
算 BC B C 的中点什么的。
垂线
算斜率什么的。
角平分线
有一个很机智的方法,考虑等腰三角形有三线合一,那么我就可以延长其中的一条边,使得这个角两条边长度相等。然后求一下新三角形的中线就行了。
如果你想通过向量的旋转来算也可以。
公式我不会。
向量的旋转
向量的旋转本质上是行向量乘一个矩阵。
其实我们只需要记得向量旋转矩阵就行了。这里是顺时针旋转 θ θ
我们还可以利用OI的特性:大胆猜想,从不证明。(轻松爆零)
这个东西就像这样:
我们先弄一个向量 (1,0) ( 1 , 0 ) ,然后逆时针旋转 θ θ ,这样新的向量就是 (cosθ,sinθ) ( c o s θ , s i n θ )
显然 a=cosθ,b=sinθ a = c o s θ , b = s i n θ
同理我们可以得到 c=−sinθ,d=cosθ c = − s i n θ , d = c o s θ
这里是逆时针旋转的!!!
接下来是凸包相关0.0
多边形
如何计算一个点是否在多边形内部?
射线法——我们以 p p 为起点作一条平行与 x x 轴的射线,若与这个多边形相交奇数次,则它在多边形内部。(实际上不一定要取平行于 x x 轴,只是方便)
但是这样做可能会有一些特殊情况。(比如射线经过一个凹多边形的内凹处,只算了1次)如果我们将端点算两次,可能会有更愚蠢的错误。(比如正方形)
因此正确的处理方式是:将所有线段设置为上闭下开,如果担心出现平行时我们可以稍微旋转一下图,或者将平行的线段设置为左闭右开。
计算多边形面积
设多边形为 P1,P2,…,Pn P 1 , P 2 , … , P n ,则
然后就是真正的凸包了。
给定点集 S S ,求最小的能把左右点包住的多边形。
来之前已经学了,但是笔记只存在硬盘里,所以挂了只能截图了。
其中“将点集倒过来再进行一次“可以认为是指上图第三个步骤中的叉积判定符号的方向相反。
这个东西学一种算法应该就可以吧。
旋转卡壳
这个东西有16种读音
其实当然要看这个在网上广为流传的东西:blog
一个凸包顺时针标号,求距离最远的两个点。
对于第一个点,我们先暴力求出离第一个点最远的是哪条边,作两条平行线。然后对于第二个点的最远边,我们可以通过同时绕第一个点旋转这两条平行线,直到一条线与另一条边重合,计算此时的距离。balabala
反正还是很简单的。更符合这个名字的题目是:
找一个面积最小的矩形,使它能够包含所有点。
老师的ppt太辣鸡了,我们还是贴论文的吧。
反正这是一种思想,有很多种卡的方法,在做题中慢慢吸收吧。
半平面交。
直线是有方向的,一条直线分割成两个半平面,一般我们选择直线左侧的半平面。
半平面交即所有这些左侧半平面的交集。显然这个轮廓一定是凸轮廓。
这个半平面交点轮廓可能是一个无限平面。因此我们通常用一个极大的方框将整个平面确定。
半平面交最简单的做法就是暴力判断- -
运用给出的多边形每相邻两点形成一条直线来切割原有多边形,如果多边形上的点 i i 在有向直线的左边或者在直线上即保存起来,否则判断此点的前一个点 i−1 i − 1 和后一个点 i+1 i + 1 是否在此直线的左边或线上,在的话分别用点i和点 i−1 i − 1 构成的直线与此时正在切割的直线相交求出交点,这个交点显然也要算在切割后剩下的多边形里,同理点 i i 和点 i+1 i + 1 。原多边形有 n n 条边,每条边都要进行切割,所以时间复杂度为 O(n2) O ( n 2 ) 。
还有一种是 O(nlogn) O ( n l o g n ) 的双端队列做法233
这个东西可以用在一些玄学的题目。
比如:区间加等差数列,区间求最大值。
每一个位置,可以表示为一个关于公差的函数。然后我们可以用这些直线组成半平面交,然后求最大值时,就相当于求对应 x x 值的函数的最大值。然后这个东西还要用线段树来维护。
我不会告诉你昨天晚上有人问过这道题的,人家用离线分治+最近点对做
还可以求多个多边形的交集的面积。。。
把所有边拆开什么的。
其实半平面交只是一个工具,很少会用到的0.0
例题BZOJ2618
圆
圆的表示方法 (x,y),r ( x , y ) , r
求圆与直线的交点:显然我们可以暴力解方程来做这个题。
然而更简单的方法是我们从圆心向直线作垂线,算出垂足位置及其到圆心位置。然后根据勾股定理可以算出弦长,又因为我们知道直线的方向,这样就可以求出两个交点。
然后要求圆与圆的交点也是很简单的0.0。设两个方程为
下面是一点习题
CF23D
一个凸四边形,三边长度一样,仅给出这三边的中点坐标,问原四边形的四个顶点坐标。
观察到有两个顶点必然是在某两条中点边的中垂线上,然后乱搞即可。貌似可以用圆来做?不清楚。
下面进入三维时代:
三维向量的叉积:
本质上是一个组成与前两个向量所在的平面的第三个的向量。
其数值为面积,符号为方向。可以把这个东西看作是一个行列式:
混合积
三维直线的表示方法
解析法:
异面直线:不平行也不相交的直线。
异面直线的距离:两条直线上距离最小的两点之间的距离,距离线一定垂直于两条直线,距离线又称为两直线的公垂线段。
我们怎么求这个东西?显然,我们可以设这个向量为 (x,y,z) ( x , y , z ) ,然后这个向量和原来的两个向量叉乘都为0,直接联立解方程组即可。
但实际上上面那个做法太stupid了!
我们只需要将原来的两个直线向量叉乘即可。
然而现在我们只求出了一个方向向量,怎么求距离?
也很简单,我们只需要在两个向量上都各取一个点,作出这两点的向量。我们用这个向量和求出的方向向量作点乘,这样就将这个向量投影到了方向上。考虑到两个异面直线垂直于这个方向,因此对这投影的贡献均为0,所以我们直接将点乘后的结果除以方向向量的模长即可。
三维直线交点?
我们要先判断它们有没有交点,然后和二维的求法类似。
平面解析式
求点到直线的距离?
将点和和法向量连线,作投影。
直线与平面的交点?
在直线上取两个点,求出到平面的距离,这样肯定能得出两个相似三角形,然后我们可以得出这两个点的比例关系,这两个点的距离,就能根据比例关系算出距离。
我就是不解方程。
两平面的交线呢?
首先求出两个平面的交线方向——将两个平面的法向量叉乘。
然后在其中一个平面里随机一条直线,那么再求这条直线与另一个平面的交点即可。
我们接下来要讲的东西是玄学。
辛普森积分:求一个不规则可中空图形的面积。
考虑这样一种方法:
我们将这个图形从左往右划分成很多个区间。考虑对每一个很小的区间用近似的方法去计算。我们在这个区间的中间画一条线,会和这个东西有一些交点,我们将第一第二个交点间距加上第三第四个交点间距加上一堆交点之间的间距,得到一个高度。然后乘上这个区间的宽,就可以了。
然而这个东西需要我们在精度和时间上进行抉择 (TLE还是WA)
那么我们考虑一种自适应积分,让它自己来判断划分。
可以这样做:令 f(x) f ( x ) 表示在x这个位置,需要计算的图形的长度是多少。
那么我们上一种方法实际上可以表示为 S=(b−a)×f(a+b2) S = ( b − a ) × f ( a + b 2 )
现在我们想看看这个东西是否较为精确,我们可以算出这个区间的左边的近似面积+这个区间右边的近似面积。
如果我们算出的这个值,和整个区间的近似面积误差小于eps,那么我们就认为现在这个值较为精确,我们可以return回去。
即如果左半部分+右半部分=全部,那么停止递归。
上面的是普通的近似积分,辛普森积分和上面的方法思想是类似的,只是将近似函数修改为:
以追风人为参考系。那么龙卷风的轨迹是一条折线。问题转化为点到折线的最短距离。
分为两部分:点到一堆点的最短距离,点到一堆线段的最短距离。
对于点,可以分为两组,每组点共线。
对于线段,也可以分为两组,每一组是一些平行的线段。
分别计算最短距离,取最小的即可。
JSOI2007合金。
N N 合金,其中每种合金有三种金属组成比例为 ai:bi:ci a i : b i : c i
M M 次询问新给定的某种合金能否全用这些合金组合而成。
N,M≤103 N , M ≤ 10 3
首先如果只有两种金属怎么做呢?
我们通过abc三个东西解方程列。
但是如果大于两种我们的方程就不够了,它不具有拓展性。
我们可以把这些金属做一个归一化,强制使同种金属的三个数字加起来是1并且合金比例之和为1.那么再考虑我们现在合金的实际比例怎么表示。
下面这题似乎更加玄学。
F(x)=∑Ni=1μixx F ( x ) = ∑ i = 1 N μ i x x
其中 0≤μi≤1 0 ≤ μ i ≤ 1 且和为1
已知 F(x)=c F ( x ) = c ,
给定 x,y,c x , y , c ,求 F(y) F ( y ) 的最大值和最小值。
看到给定的变量名,我们猜测这是(一个关于数论的题)平面上的 N N 个点,求一个凸包。
显然凸包内任何一个点可以由凸包所有的点乘系数组合而成。
组合的方式显然就是 μ μ 的取值方式。
每一组 μ μ 是图上的一个点,那么实际上要求的就是 c c 这个位置上,凸包的最高点和最低点的位置。
今天就这么多了。