2D
仿射变换
常用的变换有若干种,如刚性变换、仿射变换、透视变换。
仿射变换包括旋转,平移,缩放和剪切。由于仿射变换是线性变换,所以都可以表达为乘上矩阵的形式。
仿射变换都可以表示成如下形式:
旋转:((x, y))逆时针旋转( heta)弧度
平移:((x, y) ightarrow (x + tx, y + ty))
缩放:((x, y) ightarrow (x cdot sx, y cdot sy))
剪切:(即一个横向剪切和一个纵向剪切的符合)
大合集:
旋转卡壳
旋转卡壳最基础的应用就是求一个点集的最远点对。
显而易见的是,一个点集的最远点对显然在凸包上,然后就有了乱搞需要把凸包先求出来。
我们设计的肯定是与凸包大小相关的线性或接近线性的做法。
我们考虑一个叫“对踵点”的东西。何为对踵点?一对点,如果能被一对平行直线所切,则称其为对踵点。最远点对一定是一对对踵点。这很好证明。
考虑按一定顺序枚举凸包上的点(如逆时针),观察到对踵点将会是单调移动的!
有一张图生动说明了这个结论:
然后就可以双指针(O(n))扫了。
当然,考虑到一些细节,为了方便,我们可以用一个指针表示一条边((p_i, p_{i + 1})),用指针(j)表示(p_i)的对踵点是(p_j)(两个指针移动方向是相同的)。考虑何时(i++),何时(j++)。显然,如果(dis(p_j, p_i) < dis(p_{j + 1}, p_i)),那么应该(j++),否则(i++)。注意中间还应该更新答案。
半平面交
首先半平面交是个凸集。
因为可以把半平面看作凸集,且凸集的交还是凸集。
根据凸集上边的极角都是“循环单调的”(奇怪的名字),我们有一个如下做法(叫增量法):
1.把所有半平面按极角排序。
2.依次加入半平面,同时维护一个deque
:
- 不断在队列前端弹出半平面,直到前端两个半平面的交位于这个半平面中;
- 不断在队列后端弹出半平面,直到后端两个半平面的交位于这个半平面中;
- 在队列后端插入这个半平面。
3.删除队列两端多余的半平面:
- 不断在队列前端弹出半平面,直到前端两个半平面的交位于后端两个半平面内;
- 不断在队列后端弹出半平面,直到后端两个半平面的交位于前端两个半平面内;
复杂度是(O(nlog n))的。
注意特判半平面平行的特殊情况。
V图
即Voronoi
图。
这东西太难写了。
不开这个坑。
圆的面积并
有一个优秀的(O(n^2log n))的做法。不可能写的。
挂个链吧,万一哪一天想写写呢?
题目:SPOJ CIRU
。
可以学习一下由AekdyCoin
原创的算法。
圆的面积交
两个圆:我会做!
多个圆:这是啥???
平面图转对偶图
这个问题以前自己只会口胡一个(O(n^2log n))的扫描线做法。然而并不知道还有(O(n log n))的优秀做法。(其中点数和边数都是(O(n))级别的)
自己的辣鸡算法就不说了。
优秀做法有个名字:最左转线法(最小左转法)
。
流程大概是这样的:
你把所有边拆成两个有向边,然后把每个点的出边极角排序。
每次找一条还没有遍历的有向边((u, v)),然后取出节点(v)向外连边中,在((v, u))顺时针方向的第一条还未访问过的边。这其实就是((v, u))的下一条。一直执行这个过程直到当前边已经被访问过了为止。
要注意的是,这里不必用且不要用set
来维护,只需要vector
。这是因为除了无限区域,如果要围出一个有限区域,那么对于有限区域的每个遍历到的点,它遍历到的边是一段连续区间。
现在还有一个问题就是确定一个区域是有限面积的还是无限面积的。
由于我们把所有边都排过序,显然可以得出结论:按逆时针方向访问边,最终所形成的区域为无限区域。也就是说,你把访问的每条边((u, v))累计叉积(cross(u, v)),若最终结果为正,即为无限区域。
在这个过程中,记录一下某个区域包含哪些点,哪些边即可(只记录边也足矣)。
生动的图与解释可以访问miskcoo
点定位
其实是在平面图转对偶图的基础上做的。
大致做法就是扫描线加set
。这个set
的姿势很重要。
大致就是,如果把询问点排序后,在一个询问点转移到后一个询问点,会有一些线段被废弃了,还有一些线段开始发挥作用了。
然后一个点((x, y))所属的区域显然至少有两条线段的横坐标经过(x)。那么,我们只要把当前所有有用线段按函数值排序,函数值相同的按斜率排序,然后再找出能包住这个询问点的两条极近线段即可(必然是连续的两条,按上面的排序方法排序后在lower_bound()
一下即可)。
但是似乎有个问题。set
里面关键字的比较似乎要传入一个会变的参数curx
,即当前询问点的横坐标。这看来是个很危险的事情。因为set
里面的元素的顺序在插入的时候根据键值就已经确定了。
但是注意到,某一时刻,set
里面任意两条线段都不会严格相交(我们塞入的线段都是极小的),偏序无论如何也不会改变。也就是说,我们不必担心set
本身的问题。
最后我们就得到了一个(O(n log n))的算法了。
miskcoo棒棒哒
圆反演
反演的性质:
- 一个过反演中心的圆,反演后得到一条不过反演中心的直线;
- 一个不过反演中心的圆,反演后得到一个不过反演中心的圆;
- 一条过反演中心的直线,反演后得到本身;
- 两个相切的圆,反演后是两条平行直线;
- 一个圆和一条直线相切,反演后是两个相切的圆或是两条平行直线。
(应该没有错?)
应用:
1.cf某题;
2.若干圆交于一点,求圆并。
数值积分
常见的数值积分有Simpson
积分和Green
积分。
Simpson
:
因为有精度问题,所以要类似与迭代一样二分递归着做,然后比较一下误差。复杂度(O((b - a) ^ 2 log (b - a)))。
Green
:
不会。
皮克定理
皮克定理能计算顶点在格点上的多边形的面积,该公式为
其中(a)表示多边形内部的点数,(b)表示多边形边界上的点数,(S)表示多边形的面积。
欧拉公式
对于一个平面图,
其中(F)为区域个数,(V)为点数,(E)为边数,(C)为连通块数。
3D
三维点积、叉积、混合积
三维向量的点积(形如(a cdot b))得到的是一个标量,和二维相似。形式上就是(a_x b_x + a_y b_y + a_z b_z)。
三维向量的叉积(形如(a imes b))得到的是一个向量,其中这个向量垂直于(a, b)构成的平面,且模长等于(a, b)所夹的平行四边形的面积。
三维向量的混合积(形如((a imes b) cdot c))得到的是一个标量。容易知道,该式的值就是(a, b, c)所夹的平行六面体的有向面积。但是由于三维空间中没有左右,只有类似的左手系和右手系,而((a, b, c))构成右手系时,混合积为正。
正规的三维坐标轴((vec x, vec y, vec z))就构成右手系(可以用右手定则表示)。
注意((a, b, c))和((b, a, c))是不同的。
本质上,三维向量的混合积是下面这个矩阵的行列式:
三维平面、直线
三维平面有两种表达式:
1.一般式:
2.点法式(给出平面的一个法向量):
三维直线同样有两种表达方法:
1.两平面的交:
2.类似点斜式:
三维距离
-
点 to 点:easy
-
点 to 直线:解方程
-
点 to 平面:
自创了一个很奇怪的方法。
如图,对于一个点(A)和某个平面。
1.在平面上任取一点(B),且令向量(a = A - B)。
2.在平面上任取点(C),(D),令向量(u = C - B),(v = D - B)。
3.作叉积(w = u imes v),且令(w)过点(B)。
4.作叉积(b = w imes a),且令(b)过点(B)。
5.作叉积(c = b imes w),且令(c)过点(B)。
此时根据叉积的性质,有(w perp c) ,且(a, w, c)三向量共面。
因此,(a)可以表示为(w)和(c)的唯一线性组合(a = pw + qc(p, q in mathbb{Z}))。
则我们所需要的距离即为(p|w|)。
-
直线 to 直线:
两直线异面:考虑在直线(a)的方向向量为(vec a),(b)的方向向量为(vec b),考虑在(a)和(b)上各取一点(A),(B),则向量(B - A)在向量(a imes b)上的投影的模长即为所求。
两直线相交或平行:用投影转为二维问题;
-
直线 to 平面:
直线与平面平行的充要条件是直线的方向向量和平面的法向量垂直,不平行距离就是0。
对于平行的情况解方程即可。(甚至可以套点到平面的求法)
-
平面 to 平面:
两平面平行的充要条件是法向量平行,不平行距离就是0。
对于平行的情况解方程即可。
三维投影
有时一些几何对象会出现在同一个平面上,此时如果把这些对象都投影到这个平面上,有些东西如距离会很容易求。
这要求我们在平面上取一点(O')作为新的空间坐标系原点,并在平面上取一个过点(O')的向量(x)作为横轴,再取(y = v imes x)作为纵轴(其中(v)是平面法向量),这样能保证((x, y, v))构成一个右手系。为了方便,同常要将(x, y, v)都变成单位向量。
此时原平面上的任意点都可以表示成(x, y, v)的线性组合,注意到可以列出一个三个三元一次方程组,有唯一解。
由于投影是一个线性变换,所以对于原平面上的直线段什么的,投影后还是直的,因此只需要把相应的点或向量作一个投影变换即可。
三维凸包
这好D啊。对我来说过于hard了。日后填坑。
小声bb:听说3D问题很多都是转为2D做的!
所以,资瓷小清新,不资瓷大毒瘤!
其他
射影几何
这是个好东西。就那二维的射影几何举例。
在欧氏二维平面上,对于直线(Ax + By + C = 0),将其映射到向量空间上,即为
如果将(v)乘上一个常数(c),显然这条直线还是这条直线,我们把(v)称为其次的。
同理,对于一个点((x, y)),它显然是非其次的。
那射影几何做了一件关键的事情:把点其次化。
即点((x, y))对应的向量为
这样显然这个向量也是其次的,映射回去,还是点((x, y))。
然后根据这些定义,我们有一些性质:
1.若一个点(p)在直线(l)上,则(v_p imes v_l = 0);
2.经过两点(p_1),(p_2)的直线(v_l = v_{p_1} imes v_{p_2});
3.两直线(l_1),(l_2)的交点(v_p = v_{l_1} imes v_{l_2})。
这些性质都很容易验证。(其中( imes)代表向量叉积)
但是有一个问题,就是特殊情况。
比如3中(l_1 = x = 1, l_2 = x = 2)平行怎么办?
我们尝试去套用公式。发现交点(v_p = egin{bmatrix}0 & 1 & 0end{bmatrix} ^ T)。对应回去即为(p = (frac{0}{0}, frac{1}{0}))。
但显然不能就此了事。
所以定义这种点为假象无穷远点。
那么这种无穷远点可以写成集合形式:
而又由于有等式
根据性质1,得出结论,所有无穷远点在(egin{bmatrix}0 & 0 & 1end{bmatrix})这条线上,这条线因此交做无穷远线,即为(l_{infty})。显然这种线仅此一条。
让我们来看看射影几何有什么地方比欧氏几何高明的地方。
对偶性!
由性质2和3就可以看出。
其实还可以推出一些。
(p_1, p_2, p_3)三点共线(Leftrightarrow)((p_1 imes p_2) cdot p_3 = 0)。
(l_1, l_2, l_3)三线共点(Leftrightarrow)((l_1 imes l_2) cdot l_3 = 0)。
更多的……暂且不知道了。
习题
[icpclive 3270] Simplified GSM Network
题意:
已知一些信号站和一些城市的坐标,每个城市使用最近的信号站。
给定一些连接城市的道路和若干询问,求相应两城市间通信时最少需要转换信号站的次数。
题解:
如果看了题面中的那张图,可以很快意识到我们要求一个信号站的V图。求玩V图后就很easy了。
但是V图实在是太难写了,实际上还有更加巧妙的方法——二分。
对于一条道路((u, v)),我们定义它的权值(w)为通过这题道路通信需要转换信号的次数。
先找出他们使用的信号站分别为((a, b))。
如果(a = b),则显然(w = 0);
如果(a),(b)相邻,则显然(w = 1);
否则可以二分这条道路。
具体来说,就是取((u_x, u_y))和((v_x, v_y))的中点(m = (m_x, m_y)),然后假象有道路((u, m))和((m, v)),然后对其做同样的事情,并且令(w(u, v) = w(u, m) + w(m, v))。
最后再有边权的图上跑最短路即可。
题意:
养鸽人要监视他的鸽子,有一些鸽子在平面上,他可以在一些给定的点上设置监视器。
如果一只鸽子在某个监视器上或者在两个监视器所连直线上或者在三个监视器所连直线的三角形内则其就咕咕咕了,现在养鸽人要让所有鸽子咕咕咕,请问他最少需要设置多少监视器。
题解:
看上去适合区间dp。但是似乎会比较难写。
来考虑这个问题的实质是什么?实质是你在m个监视器中选择一个点集,使得其生成的凸包能包住(n)个鸽子所生成的凸包(这显然是充要的),且要使得凸包上的点要少。
考虑一个点如果在鸽子的凸包内,则可以不予考虑。否则对于点(i),计算出它与鸽子凸包的两个切点,如下图。
这样,对于图中的所有蓝监视器(j)(以(x')和(y)为分界),我们连一条(i)到(j)的边权为(1)的边。
这样,对于监视器做一遍floyd,求一个最短的自环即可(即(f_{i, i}))。
题意:
平面上有(n le 1000)个点,每个点都有概率(p)出现。
你可以在任意两个点之间连一条线段,两条线段不能在除了端点以外的地方相交。
你想要知道最多可以连接的线段的期望数目。
数据保证没有三点共线。
题解:
首先对于,把一个不存在三点共线的点集连边,且要连出最多的不相交的边这个问题,“据说”用“三角剖分”可以做到。
让我们先看一下三角剖分的含义,以下摘自百度百科:
【定义】三角剖分 :假设V是二维实数域上的有限点集,边e是由点集中的点作为端点构成的封闭线段, E为e的集合。那么该点集V的一个三角剖分T=(V,E)是一个平面图G,该平面图满足条件:
1.除了端点,平面图中的边不包含点集中的任何点。
2.没有相交边。
3.平面图中所有的面都是三角面,且所有三角面的合集是散点集V的凸包。
再放一张三角剖分的图。
我们回到欧拉公式(V + F - E = 1)(F中不考虑最外面的平面),变形得(E = V + F - 1)。
顶点数确定,要使得边数最大化,即要使得(F)面数最大化。那感性理解下,三角剖分必然是一种非常优秀的方式,而且的确是最优的。
由于这道题考察期望,那我们把式子列出来。为了方便,先设凸包上有k个点。
由于
即
则
先计算(3E(V - 1))。
而(V = 0)的情况需要特殊考虑,因(V = 0)的贡献理应是(0),而当贡献计入到((*))式时却是按照((1 - p) ^ n cdot (-3))计入的。
所以这一部分总贡献应该是
因此有
因此,我们是不是计算出每个凸包相应的贡献就行了呢?不用那么复杂,由于凸包上边数=点数,就计算出每条边存在某个凸包上的概率即可。
我们先钦定凸包上的边都是向逆时针方向指的。
暴力一点的想法:枚举一条边((i o j)),钦定它在凸包上,概率为(p * p),且是最下方的边,那么,我们只需要使得在这条边下面没有点即可,所以还要乘上((1 - p)^{边(i o j)下方的点数}),而上方的点无论怎样,都不用管,因为总的概率和是(1),并且所有无论上面某个点出现或不出现,对边((i o j))的贡献无影响。
那怎么处理((i o j))下方的点数?用叉积判断每个点。
但是这样的话这一部分复杂度是(O(n^3))的。优化?
一个套路,枚举(i)后对所有(vec {ip})极角排序,然后用双指针扫即可。
复杂度(O(n^2 log n))。
题意:
给定一个凸多边形的三角剖分。选择三个顶点连成三角形,使得其与尽量多的三角形相交。
要求(O(n))。
题解:
看上去不可做的一道题。
但实际上,如果考虑对偶图,说不定会发现什么。
有结论:简单多边形的三角剖分的对偶图是一棵树。(似乎不太会证?)
因此,任选三点连接成三角形与三角剖分的交,与对偶图树里面只有一个公共点的三条路径(可退化)形成双射。
于是问题转化为给定一棵树,求长度和最大的、只有一个公共点的三条路径。
dp即可。
题意:
给定平面上的(n)个点。
对于每个(1 le i le n),求出只保留前(i)个点,平面上点对的最远距离。
要求与(n)成线性或拖一只(log)。
点的生成方式是,以某种方式把生成坐标后,把点随机排列。
题解:
一个点集的最远点对距离可以用旋转卡壳来求,但本题显然不能直接求。
但是考虑到答案是单调的,所以我们先用一次旋转卡壳求出最远点对((a, b))。
那么对于(max(a, b) le i le n)的答案显然都是一样的。
那么接下去再处理(1 le i le max(a, b) - 1)的子问题即可。
为什么敢这么做?数据有随机保证。这样,我们可以假定(n)个点中任意一个点对为最远点对的概率都是一样的。
那么
通过简单的近似计算,(E(max(a, b)) approx frac {2n}{3})。
根据主定理,时间复杂度是(O(n log n))的。
题意:
给定平面上若干点和线段,线段上除两个端点外没有其他给定点。
每条线段有一个权值。
每次询问两个点,保证严格在某个区域内,输出两点间路径的所有方案中,经过最大边的最小值。
并且路径始终不能穿过无穷区域,也不能经过顶点。
题解:
就是上面所说的平面图转对偶图+点定位了。
最后求一个最大生成树再跑一个倍增。
总复杂度是一个(log)的。
看起来很简单,实则要判断无解的情况,还有无穷区域不能经过,等等这些限制。
其实题目本身还有问题,就是没有明确指出重边怎么办。
这导致我在uoj
上一直97
过不去,然后造了几组数据叉了大多数人,只剩std了。。。
这种题最恶心的是你即使知道怎么做,不要说调,还不一定码的出来。
总结
- 尽量把高维问题降到低维处理。
- 二分是计算几何中一个常用技巧,且一般都可以保证一定的精度,重要的是它能把问题转为相对可解的判定性问题。
- 暴力解方程也的确很不错啊。
- 尽量避免写实现过难的算法,化繁为简很难,但的确很重要。
- 巧妙的应用前人的公式,有些时候会非常精彩。