OI计算几何 简单学习笔记

学习平面几何,首先我们要会熟练地应用向量,其次也要知道一些基本的几何知识。(其实看看数学课本就可以了吧)

因为是看的蓝书,所以很多东西做了引用。(update:还参考了赵和旭dalao的讲义)

下面先介绍一些常用的操作:

点的定义

struct point
{
	double x,y;
	point(double x=0,double y=0):x(x),y(y){}
};

向量的定义

typedef point vec

值的判断

inline int dcmp(double x)
{	
	if(fabs(x)<eps) return 0;
	else if(x<0.0) return -1;
	else return 1;
}

向量的基本运算

vec operator + (vec a,vec b){return vec(a.x+b.x,a.y+b.y);}
vec operator - (vec a,vec b){return vec(a.x-b.x,a.y-b.y);}
vec operator * (vec a,double b){return vec(a.x*b,a.y*b);}
vec operator / (vec a,double b){return vec(a.x/b,a.y/b);}

向量的点积

这个学过高中数学必修四的应该都知道。
比如说对于\(\vec a\)\(\vec b\),集合上的定义为:\(\vec a\times \vec b\times cos \theta\) ,代数的计算方法则为\(ax*bx+ay*by\)

inline double dot(vec a,vec b){return a.x*b.x+a.y*b.y;} 

向量的长度

inline double length(vec a){return sqrt(dot(a,a));}

向量的夹角(返回值为弧度制)

inline double angle(vec a,vec b){return acos(dot(a,b)/length(a)/length(b));}

向量的叉积

对于\(\vec a\)\(\vec b\),集合上的定义为:\(\vec a\times \vec b\times sin \theta\) ,代数的计算方法则为\(ax*by-ay*bx\)
它的几何意义表示这两个向量形成的平行四边形的面积。

inline double cross(vec a,vec b){return a.x*b.y-a.y*b.x;} 

因为叉积在数值上可以表示向量a,b围成的平行四边形的面积,所以我们可以通过叉积来计算三个点围成的三角形的面积——

inline double area(point a,point b,point c){return cross(b-a,c-a);}

除此之外还有一些应用:

向量的旋转(注意是逆时针旋转)

公式为\(x_{new}=xcosa-ysina\),\(y_{new}=xsina+ycosa\),其中a为逆时针旋转的角的弧度值。

inline vec rotate(vec x,double a)
{return vec(x.x*cos(a)-x.y*sin(a),x.x*sin(a)+x.y*cos(a));}

直线的交点

inline point getline_intersection(point p,vec v,point q,vec w)
{
	vec u=p-q;
	double t=cross(w,u)/cross(v,w);
	return p+mul(v,t);
}

点到直线的距离

其实数学上有个公式叫做\(\frac{fabs(ax+by+c)}{\sqrt {k^2+1}}\),但是这个需要直线的表达式,我们可以利用叉积来更方便地进行计算。( 也就是用平行四边形的面积除以底)

inline double distance(point p,point a,point b)//a,b组成的是边
{
	vec v1=b-a,v2=p-a;
	return fabs(cross(v1,v2))/length(v1);//如果不取绝对值,得到的是有向距离
}

多边形的计算(同时适用于凸多边形,凹多边形)

其实就是从一个顶点开始,将多边形划分成若干个三角形进行计算。(从p0开始划分)

inline double Area(point* p,int n)
{
	double cur_area=0
	for(int i=1;i<n-1;i++)
		cur_area+=cross(p[i]-p[0],p[i+1]-p[0]);
	return cur_area/2;
}

线段相交判定

inline bool whether_intersection(point a1,point a2,point b1,point b2)
{
	double c1=cross(a2-a1,b1-a1);
	double c2=cross(a2-a1,b2-a1);
	double c3=cross(b2-b1,a2-b1);
	double c4=cross(b2-b1,a1-b1);
	return (dcmp(c1)*dcmp(c2)<0)&&(dcmp(c3)*dcmp(c4)<0);
}

多边形的重心

把一个多边形剖分成很多的三角形,依次给每个三角形求重心,按照面积加权求平均就是该多边形的重心。

判断一个点是否在多边形内

从该点随便引(一般都是竖直吧qwq)一条射线,计算它与多边形所有边的交点的数目,如果是奇数个就在多边形内,如果是偶数个就在多边形外。(在线段的两端点上相交不算有交点)
但是要注意一下细节

求两圆的交点

求两圆的内(外)公切线

如果一条直线和两个圆都相切,这条直线叫做两个圆的公切线。如果两圆在公切线的同侧,称这条公切线为两圆的外公切线,如果两圆分别在公切线的两侧,称这条公切线为两圆的内公切线。

求凸包

懒得省事了,直接放一个模板好了qwqwq

inline bool cmp(struct Node a,struct Node b)
{
    double A=atan2((a.y-t[1].y),(a.x-t[1].x));
    double B=atan2((b.y-t[1].y),(b.x-t[1].x));
    if(A!=B) return A<B;
    else return a.x<b.x;
}
inline double cross(Node a,Node b,Node c){return (a.x-c.x)*(b.y-c.y)-(a.y-c.y)*(b.x-c.x);}
inline void solve()
{
    t[0]=(Node){0x3f3f3f3f,0x3f3f3f3f};
    int k=0;
    for(int i=1;i<=n;i++)
        if(t[i].y<t[0].y||(t[i].y==t[0].y&&t[i].x<t[0].x))
            t[0]=t[i],k=i;
    swap(t[1],t[k]);
    sort(&t[2],&t[1+n],cmp);
    s[0]=t[1],s[1]=t[2];
    top=1;
    for(int i=3;i<=n;i++)
    {
        while(top&&cross(s[top-1],t[i],s[top])>=0.0) top--;
        s[++top]=t[i];
    }
}

动态凸包:(请看我的这篇博客

关键代码:(我这里使用的是极角排序)

inline void solve(Node x)
{
    nxt=st.lower_bound(x);
    if(nxt==st.end()) nxt=st.begin();
    pre=get_pre(nxt);
    if(cross((*nxt)-(*pre),x-(*pre))>=0) return;
    cur_ans-=get_len((*nxt)-(*pre));
    cur_ans+=get_len(x-(*nxt))+get_len(x-(*pre)); 
    st.insert(x);
    tmp=get_pre(pre);
    while(cross(x-(*pre),(*pre)-(*tmp))>=0)
    {
        cur_ans-=get_len(x-(*pre))+get_len((*pre)-(*tmp));
        cur_ans+=get_len(x-(*tmp));
        st.erase(pre);
        pre=tmp;
        tmp=get_pre(pre);
    }
    tmp=get_nxt(nxt);
    while(cross(x-(*nxt),(*nxt)-(*tmp))<=0)
    {
        cur_ans-=get_len(x-(*nxt))+get_len((*nxt)-(*tmp));
        cur_ans+=get_len(x-(*tmp));
        st.erase(nxt);
        nxt=tmp;
        tmp=get_nxt(nxt);
    }
    return;
}

点在多边形内的判定

method1 射线法

就是从某一个判定点出发,任意引出一条射线。如果和边界相交奇数次,说明点在多边形内。如果相交偶数次,说明点在多边形外。注意射线如果在端点处和多边形相交,或者穿过一条完整的边,则需要重新引出一条射线QAQ

method2 转角法

我们把多边形的每条边的转角加起来,如果是360度,说明在多边形内。如果是0度,说明在多边形外。如果是180度,说明在多边形的边界上。优化的操作如下:假想有一条向右的射线,统计多边形穿过这条射线正反多少次,把这个数基座绕数,逆时针穿过时+1,顺时针穿过时-1.

旋转卡壳

半平面交

咕咕咕

posted @ 2018-11-24 20:50  风浔凌  阅读(898)  评论(0编辑  收藏  举报