接触ACM大概有一年了,这一年来,学习了不少算法,但是,像我们这种弱校,教练是挂名的,算法都是师兄们一代又一代流传下来的,基本上师兄们会什么算法,就教我们什么算法了。我记得我高中的数学老师说过:“你们要超越老师,不然一代又一代传下去,会一直衰落的。”我们师兄流传下来的算法基本上都是图论,数据结构。而数论、几何这些方面存在着一个很大的缺口,每次遇到这类型的题目也只能望而生畏了。于是我决定要向这些方向进军,尝试找到新的突破。即使是弱校,也不能阻止我们热爱ACM。
计算几何
我看了算法导论和lrj的黑书,虽然黑书上有些地方暂时还没看懂,但是还算略有小成。
1、点、线段的表示:
点的坐标表示我么很容易想到用结构体:
typedef struct _point
{
int x,y;
}Point;
而线段,我们可以用两个点表示。
2、如何判断两条直线、线段是否相交:
用高中解析几何的知识我们很容易想到判断两条直线是否相交,只需要用求出两条直线的斜率,判断斜率是否相等就行了。如果是线段,只需要求出两直线交点,判断是否在两条线段两端点之间就行。然而,实际上由于在求斜率和交点时浮点型的数值运算需要考虑精度问题,而且不能体现算法的美。
在计算几何中,我们常常将问题转化为向量的关系。
假设向量a=(x1,y1),向量b=(x2,y2),只需要判断x1*y2-x2*y1是否等于0就可以判断两直线是否相交了。
但是对于线段,我们要怎样判断是否相交呢?
我们先在这里引入“向量的左右”这一个概念。如下图1,假设我们咱在向量p0p1的起点p0,面向p0p1向量的正方向,走手边即左侧,右手边为右侧。
位于向量逆时针方向的向量,我们称之为“右手螺旋”,如上图2,反之成为“左手螺旋”,如上图3。
我们在判断两线段是否相交时,只需要判断两向量是否相互“跨越”对方。所谓跨越,即一个向量的两个端点分别位于另一个向量的左右手方向。如下图:
图4中,两向量并没有相互“跨越”,因为p0和p1同时在向量p2p3的右侧。图5中,两向量互相“跨越”了对方,所以我们就可以说线段p0p1和线段p2p3相交了。
3、点积和叉积:
但是,我们该如何表示点在向量的左右方向呢?
这是我们介绍一个在计算几何里很重要的运算:点积和叉积。
回忆高中所学的知识,假设p0为(0,0),我们知道p1Xp2是(0,0),p1,p2和p1+p2所形成的平行四边形的有面积,注意这里的是“有向面积”,如下图阴影部分。
p1Xp2 = x1y2 - x2y1 = |p1|*|p2|*sinθ
如果p1Xp2为正数,那么相对于(0,0),p2位于p1的右手螺旋方向,如果p1Xp2为负数,那么p2位于p1的左手螺旋方向。于是我们运用叉积,就能判断点在向量的左右方向了。
int cross (Point p0,Point p1,Point p2)
{//求p0p1和p0p2的叉积
return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);
}
现在我们来考虑特殊情况:
假设向量中的一个点在另一条向量上,如上图7,此时,p0p1Xp0p2=0,此时该如何判断呢?很容易能想到只要用坐标判断p2是否在p0和p1之间就行。另外,我们还有另一种方法,用向量的点积能判断。
向量p1·p2 = |p1|*|p2|*cosθ,如下图:
只需要判断向量p2p0和p2p1的点积就能判断点是否在线段上。当点积小于0,表示p2在线段p0p1上,当点积大于0,表示不在线段上。