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


 

 

 

 

 

 

 

 

 

ACM 很全的计算几何模板 基础部分 1.几何公式 5 1.1三角形 5 1.2四边形 5 1.3正n边形 5 1.4圆 5 1.5棱柱 6 1.6棱锥 6 1.7棱台 6 1.8圆柱 6 1.9圆锥 6 1.10圆台 7 1.11球 7 1.12球台 7 1.13球扇形 7 2.直线与线段 7 2.0预备函数 7 2.1判三点是否共线 8 2.2判点是否在线段上 9 2.3判断两点在线段的同一侧 9 2.4判断两点是否在线段的异侧 9 2.5求点关于直线的对称点 10 2.7判断两线段是否相交 10 2.7.1常用版 10 2.7.2不常用版 11 2.8 求两条直线的交点 11 2.9点到直线的最近距离 12 2.10点到线段的最近距离 12 3.多边形 12 3.0 预备浮点函数 12 3.1判定是否是凸多边形 13 3.2判定点是否在多边形内 14 3.3 判定一条线段是否在一个任意多边形内 15 4. 三角形 16 4.0预备函数 16 4.1求三角形的外心 17 4.2求三角形内心 17 4.3求三角形垂心 17 5. 圆 18 5.0预备函数 18 5.1判定直线是否与圆相交 19 5.2判定线段与圆相交 19 5.3判圆和圆相交 19 5.4计算圆上到点p最近点 19 5.5计算直线与圆的交点 20 5.6计算两个圆的交点 20 6. 球面 21 6.0给出地球经度纬度,计算圆心角 21 6.1已知经纬度,计算地球上两点直线距离 21 6.2已知经纬度,计算地球上两点球面距离 21 7. 三维几何的若干模板 22 7.0预备函数 22 7.1判定三点是否共线 23 7.2判定四点是否共面 23 7.1判定点是否在线段上 23 7.2判断点是否在空间三角形上 24 7.3判断两点是否在线段同侧 24 7.4判断两点是否在线段异侧 25 7.5判断两点是否在平面同侧 25 7.6判断两点是否在平面异侧 25 7.7判断两空间直线是否平行 25 7.8判断两平面是否平行 26 7.9判断直线是否与平面平行 26 7.10判断两直线是否垂直 26 7.11判断两平面是否垂直 26 7.12判断两条空间线段是否相交 27 7.13判断线段是否与空间三角形相交 27 7.14计算两条直线的交点 28 7.15计算直线与平面的交点 28 7.16计算两平面的交线 29 7.17点到直线的距离 29 7.18 计算点到平面的距离 29 7.19计算直线到直线的距离 30 7.20空间两直线夹角的cos值 30 7.21两平面夹角的cos值 30 7.22直线与平面夹角sin值 31 1.最远曼哈顿距离 31 2. 最近点对 32 3. 最近点对 34 4. 最小包围圆 36 5. 求两个圆的交点 39 6. 求三角形外接圆圆心 40 7. 求凸包 42 8.凸包卡壳旋转求出所有对踵点、最远点对 44 9. 凸包+旋转卡壳求平面面积最大三角 47 10. Pick定理 50 11. 求多边形面积和重心 51 12. 判断一个简单多边形是否有核 52 13. 模拟退火 54 14. 六边形坐标系 56 15. 用一个给定半径的圆覆盖最多的点 60 16. 不等大的圆的圆弧表示 62 17. 矩形面积并 62 18. 矩形的周长并 66 19. 最近圆对 70 20. 求两个圆的面积交 74
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值