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

看了刘汝佳的《算法竞赛入门经典》之训练指南这本书的几何问题之后,受益匪浅,然后根据我自己平时的几何知识写下这篇关于二维几何基础的文章,希望对大家有用。

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

 

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。下面是点积计算方法,以及用点积计算向量长度和夹角的函数。

//点积计算方法
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组成的三角形的有向面积的两倍。叉积的计算方法及三角形面积的两倍的计算方法如下:

 

//就算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为逆时针旋转的角。代码如下:

 

//向量的旋转
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度以后把长度归一化。

//计算向量单位法线
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)。

代码如下:

//直线交点公式
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;
}


 

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

//点到直线的距离
double DistanceToLine(Point P,Point A,Point B)
{
    Point v1=B-A,v2=P-A;
    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);
}


 

 

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

 

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


 

另外一种实现点和向量的方法,即使用C++里的复数。

#include<complex>
using namespace std;
typedef complex<double> Point;
typedef Point Vector;

 

这样定义之后,我们拥有了构造函数、加减法和数量积。用real(p)和imag(p)访问实部和虚部,conj(p)返回共轭复数,即conj(a+bi)=a-bi。相关函数如下:

double Dot(Point A,Point B)
{
    return real(conj(A)*B);
}
double Cross(Point A,Point B)
{
    return imag(conj(A)*B);
}
Point Rotate(Point A,double rad)
{
    return A*exp(Point(0,rad));
}


 

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

//判断线段相交(不含端点)
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;
}


 

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

//判断一个点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个三角形,然后把面积加起来。代码如下:

//计算多边形的面积
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]点为划分顶点,一方面可以少算两个叉积,另一方面也减少乘法溢出的可能性,还不用特殊处理。代码如下:

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;
}


 

 

 将度转化为弧度代码:

double torad(double x)//将度转为弧度
{
    double pi = acos(-1);
    return x*pi/180;
}


 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值