ACM几何问题基础知识讲解(附代码)

首先我们要理解什么是向量,向量就是有大小和方向的量。在平面坐标系中,向量用x,y表示。等于向量起点到终点的位移。以下是它们的常用定义:

 

[cpp] view plain copy print ?
  1. struct Point  
  2. {  
  3.     double x,y;  
  4.     Point(double x=0,double y=0):x(x),y(y){}//构造函数  
  5. };  
  6. typedef Point Vector;  
  7.   
  8. //向量+向量=向量,点+向量=点  
  9. Point operator+(Point A,Point B)  
  10. {  
  11.     return Point(A.x+B.x,A.y+B.y);  
  12. }  
  13.   
  14. //点-点=向量  
  15. Point operator-(Point A,Point B)  
  16. {  
  17.     return Point(A.x-B.x,A.y-B.y);  
  18. }  
  19.   
  20.   
  21. //向量*数=向量  
  22. Point operator*(Point A,double p)  
  23. {  
  24.     return Point(A.x*p,A.y*p);  
  25. }  
  26.   
  27.   
  28. //向量/数=向量  
  29. Point operator/(Point A,double p)  
  30. {  
  31.     return Point(A.x/p,A.y/p);  
  32. }  
  33. double eps=1e-10;  
  34.   
  35. //如果等于0,返回0,小于0返回-1,大于0,返回1  
  36. int dcmp(double x)  
  37. {  
  38.     if(fabs(x)<eps)return 0;else return x<0?-1:1;  
  39. }  
  40.   
  41. //判断是否相等  
  42. bool operator==(const Point& a,const Point &b)  
  43. {  
  44.     return dcmp(a.x-b.x)==0&&dcmp(a.y-b.y)==0;  
  45. }  
  46.   
  47. int main()  
  48. {  
  49.     Point a,b,c;  
  50.     double p=2;  
  51.     a.x=4;  
  52.     a.y=3;  
  53.     b.x=2;  
  54.     b.y=5;  
  55.     c=a+b;//向量+向量=向量  
  56.     c=a-b;//点-点=向量  
  57.     c=a/p;//向量/数=向量  
  58.     c=a*p;//向量*数=向量  
  59.     return 0;  
  60. }  


 

基本运算

点积是两个向量v和w的点积等于二者长度的乘积在乘上它们夹角的余弦。如果两向量垂直,点积就等于0。两个向量OA和OB的点积等于xa*xb+ya*yb。下面是点积计算方法,以及用点积计算向量长度和夹角的函数。

[cpp] view plain copy print ?
  1. //点积计算方法  
  2. double Dot(Point A,Point B)  
  3. {  
  4.     return A.x*B.x+A.y*B.y;  
  5. }  
  6. //点到原点的长度  
  7. double Lenth(Point A)  
  8. {  
  9.     return sqrt(Dot(A,A));  
  10. }  
  11. //角度计算  
  12. double Angle(Point A,Point B)  
  13. {  
  14.     return acos(Dot(A,B)/Lenth(A)/Lenth(B));  
  15. }  


 

叉积就是两个向量v和w组成的三角形的有向面积的两倍。叉积的计算方法及三角形面积的两倍的计算方法如下:

 

[cpp] view plain copy print ?
  1. //就算OA和OB的叉积  
  2. double Cross(Point A,Point B)  
  3. {  
  4.     return A.x*B.y-A.y*B.x;  
  5. }  
  6.   
  7. //计算三角形的面积的两倍  
  8. double Area(Point A,Point B,Point C)  
  9. {  
  10.     return Cross(B-A,C-A);  
  11. }  


 

向量的旋转。向量可以绕起点旋转,公式为x'=x*cosa-y*sina,y'=x*sina+y*cosa。其中a为逆时针旋转的角。代码如下:

 

[cpp] view plain copy print ?
  1. //向量的旋转  
  2. Point Rotate(Point A,double rad)//rad是弧度  
  3. {  
  4.     return Point(A.x*cos(rad)-A.y*sin(rad),A.x*sin(rad)+A.y*cos(rad));  
  5. }  


 

下面的函数计算向量的单位法线,即左转90度以后把长度归一化。

[cpp] view plain copy print ?
  1. //计算向量单位法线  
  2. Point Normal(Point A)  
  3. {  
  4.     double l=Lenth(A);  
  5.     return Point(-A.y/l,A.x/l);  
  6. }  


 

点和直线 

直线上一点可以用直线上一点p0和方向向量v来表示。直线上所有点p=p0+t*v.其中t为参数。如果已知直线上的两个不同点A和B,则方向向量为B-A,所以参数方程A+(B-A)*t。对于直线t没有限制,射线t>0,线段0<t<1。

直线交点。设直线分别为P+tv和Q+tw,设向量u=P-Q,交点在第一条直线的参数为t1,第二条直线的参数为t2,则x和y坐标可以列出一个方程,解得:

t1=cross(w,u)/cross(v,w),t2=cross(v,u)/cross(v,w)。

代码如下:

[cpp] view plain copy print ?
  1. //直线交点公式  
  2. Point  GetLineIntersection(Point P,Point V,Point Q,Point W)  
  3. {  
  4.     Point u=P-Q;  
  5.     double t=Cross(W,u)/Cross(V,W);  
  6.     return P+V*t;  
  7. }  


 

点到直线的距离。点到直线的距离是一个常用函数,可以用叉积算出,即用平行四边形的面积除以底,代码如下:

[cpp] view plain copy print ?
  1. //点到直线的距离  
  2. double DistanceToLine(Point P,Point A,Point B)  
  3. {  
  4.     Point v1=B-A,v2=P-A;  
  5.     return fabs(Cross(v1,v2))/Lenth(v1);  
  6. }  


 

点到线段的距离有两种情况,代码如下:

[cpp] view plain copy print ?
  1. //点到线段的距离  
  2. double DistanceToSegment(Point P,Point A,Point B)  
  3. {  
  4.     if(A==B) return Lenth((P-A));  
  5.     Point v1=B-A,v2=P-A,v3=P-B;  
  6.     if(dcmp(Dot(v1,v2))<0)return Lenth(v2);  
  7.     else if(dcmp(Dot(v1,v3))>0)return Lenth((v3));  
  8.     else return fabs(Cross(v1,v2))/Lenth(v1);  
  9. }  


 

 

点在直线上的投影,代码如下:

 

[cpp] view plain copy print ?
  1. //点P在直线AB上的投影  
  2. Point GetLineProjecton(Point P,Point A,Point B)  
  3. {  
  4.     Point v=B-A;  
  5.     return A+v*(Dot(v,P-A)/Dot(v,v));  
  6. }  


 

线段相交判定。给定两条线段,判断是否相交。我们定义“规范相交”为两线段恰好有一个公共点,且不在任何一条线段的端点。线段规范相交的充要条件是:每条线段的两个端点都在另一条线段的两侧。代码如下:

[cpp] view plain copy print ?
  1. //判断线段相交(不含端点)  
  2. bool SegmentProperIntersection(Point a1,Point a2,Point b1,Point b2)  
  3. {  
  4.     double c1=Cross(a2-a1,b1-a1),c2=Cross(a2-a1,b2-a1),  
  5.     c3=Cross(b2-b1,a1-b1),c4=Cross(b2-b1,a2-b1);  
  6.     return dcmp(c1)*dcmp(c2)<0&&dcmp(c3)*dcmp(c4)<0;  
  7. }  


 

如果允许在端点出相交,情况就比较复杂了,有可能共线,还有可能某个端点在另外一条线段上。为了判断上述情况是否发生,还需要如下一段判断一个点是否在一条线段上(不含端点)的代码:

[cpp] view plain copy print ?
  1. //判断一个点P是否在一条线段a1a2上  
  2. bool OnSegment(Point p,Point a1,Point a2)  
  3. {  
  4.     return dcmp(Cross(a1-p,a2-p))==0&&dcmp(Dot(a1-p,a2-p));  
  5. }  


 

多边形

计算多边形的面积。如果多变形是凸的,可以从第一个顶点出发把凸多边形分成n-2个三角形,然后把面积加起来。代码如下:

[cpp] view plain copy print ?
  1. //计算多边形的面积  
  2. double ConvexPolygonArea(Point* p,int n)  
  3. {  
  4.     double area=0;  
  5.     for(int i=1;i<n-1;i++)  
  6.     area+=Cross(p[i]-p[0],p[i+1]-p[0]);  
  7.     return area/2;  
  8. }  


 

可以另取p[0]点为划分顶点,一方面可以少算两个叉积,另一方面也减少乘法溢出的可能性,还不用特殊处理。代码如下:

[cpp] view plain copy print ?
  1. double PolygonArea(Point* p,int n)//p是一个结构体数组  
  2. {  
  3.     double area=0;  
  4.     for(int i=1;i<n-1;i++)  
  5.     area+=Cross(p[i]-p[0],p[i+1]-p[0]);  
  6.     return area/2;  
  7. }  
  8. int main()  
  9. {  
  10.     Point a[4];  
  11.     a[0].x=0;  
  12.     a[0].y=0;  
  13.     a[1].x=3;  
  14.     a[1].y=0;  
  15.     a[2].x=3;  
  16.     a[2].y=3;  
  17.     a[3].x=0;  
  18.     a[3].y=3;  
  19.     double area=PolygonArea(a,4);  
  20.     printf("%lf",area);  
  21.     return 0;  
  22. }  

你可能感兴趣的:(ACM,几何问题)