您的位置:首页 > 编程语言

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

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

[cpp]
view plaincopyprint?

struct Point
{
double x,y;
Point(double x=0,double y=0):x(x),y(y){}//构造函数
};
typedef Point Vector;

//向量+向量=向量,点+向量=点
Point operator+(Point A,Point B)
{
return Point(A.x+B.x,A.y+B.y);
}

//点-点=向量
Point operator-(Point A,Point B)
{
return Point(A.x-B.x,A.y-B.y);
}

//向量*数=向量
Point operator*(Point A,double p)
{
return Point(A.x*p,A.y*p);
}

//向量/数=向量
Point operator/(Point A,double p)
{
return Point(A.x/p,A.y/p);
}
double eps=1e-10;

//如果等于0,返回0,小于0返回-1,大于0,返回1
int dcmp(double x)
{
if(fabs(x)<eps)return 0;else return x<0?-1:1;
}

//判断是否相等
bool operator==(const Point& a,const Point &b)
{
return dcmp(a.x-b.x)==0&&dcmp(a.y-b.y)==0;
}

int main()
{
Point a,b,c;
double p=2;
a.x=4;
a.y=3;
b.x=2;
b.y=5;
c=a+b;//向量+向量=向量
c=a-b;//点-点=向量
c=a/p;//向量/数=向量
c=a*p;//向量*数=向量
return 0;
}

struct Point
{
double x,y;
Point(double x=0,double y=0):x(x),y(y){}//构造函数
};
typedef Point Vector;

//向量+向量=向量,点+向量=点
Point operator+(Point A,Point B)
{
return Point(A.x+B.x,A.y+B.y);
}

//点-点=向量
Point operator-(Point A,Point B)
{
return Point(A.x-B.x,A.y-B.y);
}

//向量*数=向量
Point operator*(Point A,double p)
{
return Point(A.x*p,A.y*p);
}

//向量/数=向量
Point operator/(Point A,double p)
{
return Point(A.x/p,A.y/p);
}
double eps=1e-10;

//如果等于0,返回0,小于0返回-1,大于0,返回1
int dcmp(double x)
{
if(fabs(x)<eps)return 0;else return x<0?-1:1;
}

//判断是否相等
bool operator==(const Point& a,const Point &b)
{
return dcmp(a.x-b.x)==0&&dcmp(a.y-b.y)==0;
}

int main()
{
Point a,b,c;
double p=2;
a.x=4;
a.y=3;
b.x=2;
b.y=5;
c=a+b;//向量+向量=向量
c=a-b;//点-点=向量
c=a/p;//向量/数=向量
c=a*p;//向量*数=向量
return 0;
}


基本运算

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

[cpp]
view plaincopyprint?

//点积计算方法
double Dot(Point A,Point B)
{
return A.x*B.x+A.y*B.y;
}
//点到原点的长度
double Lenth(Point A)
{
return sqrt(Dot(A,A));
}
//角度计算
double Angle(Point A,Point B)
{
return acos(Dot(A,B)/Lenth(A)/Lenth(B));
}

//点积计算方法
double Dot(Point A,Point B)
{
return A.x*B.x+A.y*B.y;
}
//点到原点的长度
double Lenth(Point A)
{
return sqrt(Dot(A,A));
}
//角度计算
double Angle(Point A,Point B)
{
return acos(Dot(A,B)/Lenth(A)/Lenth(B));
}


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

[cpp]
view plaincopyprint?

//就算OA和OB的叉积 double Cross(Point A,Point B) { return A.x*B.y-A.y*B.x; } //计算三角形的面积的两倍 double Area(Point A,Point B,Point C) { return Cross(B-A,C-A); }
//就算OA和OB的叉积
double Cross(Point A,Point B)
{
return A.x*B.y-A.y*B.x;
}

//计算三角形的面积的两倍
double Area(Point A,Point B,Point C)
{
return Cross(B-A,C-A);
}


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

[cpp]
view plaincopyprint?

//向量的旋转
Point Rotate(Point A,double rad)//rad是弧度
{
return Point(A.x*cos(rad)-A.y*sin(rad),A.x*sin(rad)+A.y*cos(rad));
}

//向量的旋转
Point Rotate(Point A,double rad)//rad是弧度
{
return Point(A.x*cos(rad)-A.y*sin(rad),A.x*sin(rad)+A.y*cos(rad));
}


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

[cpp]
view plaincopyprint?

//计算向量单位法线 Point Normal(Point A) { double l=Lenth(A); return Point(-A.y/l,A.x/l); }

//计算向量单位法线
Point Normal(Point A)
{
double l=Lenth(A);
return Point(-A.y/l,A.x/l);
}


点和直线

直线上一点可以用直线上一点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 plaincopyprint?

//直线交点公式
Point GetLineIntersection(Point P,Point V,Point Q,Point W)
{
Point u=P-Q;
double t=Cross(W,u)/Cross(V,W);
return P+V*t;
}

//直线交点公式
Point  GetLineIntersection(Point P,Point V,Point Q,Point W)
{
Point u=P-Q;
double t=Cross(W,u)/Cross(V,W);
return P+V*t;
}


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

[cpp]
view plaincopyprint?

//点到直线的距离 double DistanceToLine(Point P,Point A,Point B) { Point v1=B-A,v2=P-A; return fabs(Cross(v1,v2))/Lenth(v1); }

//点到直线的距离
double DistanceToLine(Point P,Point A,Point B)
{
Point v1=B-A,v2=P-A;
return fabs(Cross(v1,v2))/Lenth(v1);
}


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

[cpp]
view plaincopyprint?

//点到线段的距离 double DistanceToSegment(Point P,Point A,Point B) { if(A==B) return Lenth((P-A)); Point v1=B-A,v2=P-A,v3=P-B; if(dcmp(Dot(v1,v2))<0)return Lenth(v2); else if(dcmp(Dot(v1,v3))>0)return Lenth((v3)); else return fabs(Cross(v1,v2))/Lenth(v1); }
//点到线段的距离
double DistanceToSegment(Point P,Point A,Point B)
{
if(A==B) return Lenth((P-A));
Point v1=B-A,v2=P-A,v3=P-B;
if(dcmp(Dot(v1,v2))<0)return Lenth(v2);
else if(dcmp(Dot(v1,v3))>0)return Lenth((v3));
else return fabs(Cross(v1,v2))/Lenth(v1);
}


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

[cpp]
view plaincopyprint?

//点P在直线AB上的投影
Point GetLineProjecton(Point P,Point A,Point B)
{
Point v=B-A;
return A+v*(Dot(v,P-A)/Dot(v,v));
}

//点P在直线AB上的投影
Point GetLineProjecton(Point P,Point A,Point B)
{
Point v=B-A;
return A+v*(Dot(v,P-A)/Dot(v,v));
}


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

[cpp]
view plaincopyprint?

//判断线段相交(不含端点) bool SegmentProperIntersection(Point a1,Point a2,Point b1,Point b2) { double c1=Cross(a2-a1,b1-a1),c2=Cross(a2-a1,b2-a1), c3=Cross(b2-b1,a1-b1),c4=Cross(b2-b1,a2-b1); return dcmp(c1)*dcmp(c2)<0&&dcmp(c3)*dcmp(c4)<0; }
//判断线段相交(不含端点)
bool SegmentProperIntersection(Point a1,Point a2,Point b1,Point b2)
{
double c1=Cross(a2-a1,b1-a1),c2=Cross(a2-a1,b2-a1),
c3=Cross(b2-b1,a1-b1),c4=Cross(b2-b1,a2-b1);
return dcmp(c1)*dcmp(c2)<0&&dcmp(c3)*dcmp(c4)<0;
}


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

[cpp]
view plaincopyprint?

//判断一个点P是否在一条线段a1a2上 bool OnSegment(Point p,Point a1,Point a2) { return dcmp(Cross(a1-p,a2-p))==0&&dcmp(Dot(a1-p,a2-p)); }
//判断一个点P是否在一条线段a1a2上
bool OnSegment(Point p,Point a1,Point a2)
{
return dcmp(Cross(a1-p,a2-p))==0&&dcmp(Dot(a1-p,a2-p));
}


多边形

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

[cpp]
view plaincopyprint?

//计算多边形的面积
double ConvexPolygonArea(Point* p,int n)
{
double area=0;
for(int i=1;i<n-1;i++)
area+=Cross(p[i]-p[0],p[i+1]-p[0]);
return area/2;
}

//计算多边形的面积
double ConvexPolygonArea(Point* p,int n)
{
double area=0;
for(int i=1;i<n-1;i++)
area+=Cross(p[i]-p[0],p[i+1]-p[0]);
return area/2;
}


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

[cpp]
view plaincopyprint?

double PolygonArea(Point* p,int n)//p是一个结构体数组
{
double area=0;
for(int i=1;i<n-1;i++)
area+=Cross(p[i]-p[0],p[i+1]-p[0]);
return area/2;
}
int main()
{
Point a[4];
a[0].x=0;
a[0].y=0;
a[1].x=3;
a[1].y=0;
a[2].x=3;
a[2].y=3;
a[3].x=0;
a[3].y=3;
double area=PolygonArea(a,4);
printf("%lf",area);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: