zoukankan      html  css  js  c++  java
  • [学习笔记] 计算几何

    0. 前置芝士

    0.1. 误差

    由于浮点数绝对值越大精度越不精确,需要尽量避免 大数与小数的加减

    尽量少用三角函数,除法,开方,求幂,取对数运算。

    对于判断浮点数的大小关系,有以下两种方案:

    • 定义 sgn(const double x) 计算浮点数的符号:

      const double eps=1e-9;
      inline int sgn(const double x) {
      	return x<-eps?-1:x>eps;
      }
      

      利用这个函数就有:

      inline int dcmp(const double x,const double y) {
          return sgn(x-y);
      }
      
    • 乘上 (10) 的幂转化成整数。

    0.2. 反三角函数

    需要注意 定义域

    double x=1.000001;
    if(fabs(x-1.0)<eps || fabs(x+1.0)<eps)
    	x=round(x);
    double acx=acos(x);
    

    对于反正切函数,最好使用 cmath::atan2(y,x)

    1. 向量与线

    1.1. 转角公式

    将向量 ((x,y)=(rcos alpha,rsin alpha)) 逆时针旋转 (eta)

    [(rcos(alpha+eta),rsin (alpha+eta)) ]

    [=(xcos eta-ysin eta,xsin eta+ycos eta) ]

    1.2. 向量外积

    (|a imes b|) 等于由向量 (a) 和向量 (b) 构成的平行四边形的面积。可以理解成一个行列式 (egin{vmatrix} a.x & b.x \ a.y & b.y end{vmatrix})

    根据 (a imes b) 的正负还可以判断 (a,b) 的位置关系:

    • (a imes b=0)。矩阵行列式为 (0),那么可以相互线性表示。(a,b) 共线。
    • (a imes b>0)。将 (b) 转到 (a) 的角度顺时针方向较小。
    • (a imes b<0)。将 (b) 转到 (a) 的角度逆时针方向较小。

    1.3. 点到直线与线段距离

    用外积求出四边形面积,再除以 (p_1p_2)

    线段需要特判一下 (p_0)(p_1p_2) 的垂足是否落在线段 (p_1p_2) 上。

    1.4. 求直线交点

    先利用跨立实验判断是否相交,再利用面积比计算。图是我嫖的。

    需要注意的是,在跨立实验中是否传入两点重合的直线或线段。

    2. 多边形

    2.1. 多边形面积

    任意选点进行三角剖分,即加上多边形相邻点和选点 (O) 的叉积。感性理解一下当 (p_i)(p_{i+1}) 的右手边时加入面积,反之减去面积。

    2.2. 射线法

    用于判断点 (p) 是否在多边形内部。从点 (p) 引一条射线,若与多边形有奇数个交点则在内部,反之在外部。它还可以处理复杂多边形,就像这样:

    不过有引出射线与多边形端点有交点的情况。事实上像这样:

    bool inAngle(vec a,vec b,const vec p) {
    	if(sgn(cross(a,b))<0) swap(a,b);
    	return sgn(cross(a,p))>=0 and sgn(cross(b,p))<=0;
    }
    bool InPolygon_1(const vec p,const vector <vec> poy) {
    	int n=poy.size();
    	double r=(rand()/double(RAND_MAX)-0.5)*2*pi;
    	vec v(cos(r),sin(r));
    	bool ret=0; 
    	for(int i=0;i<n;++i) {
    		if(onSeg(poy[i],poy[(i+1)%n],p))
    			return 1;
    		ret=inAngle(poy[i]-p,poy[(i+1)%n]-p,v)?(!ret):ret;
    	}
    	return ret;
    }
    

    我们将交点两端的边都计算进去,所以不会有问题。

    但是如果射线与多边形的边重合就会出错… 不过这种情况很难发生就是了,因为出题人并不知道我是怎么随机射线的…

    2.3. 回转数算法

    用于判断点 (d) 是否在多边形内部。沿多边形走一圈,累计绕点 (d) 转了多少角度。

    • (0)。多边形外。
    • (±pi)。多边形上。
    • (±2pi)。多边形内。

    在实现中并不需要计算转角,只用转象限。最后保证多边形外的点答案为 (0) 即可。考虑同样从 (p_i) 转到 (p_{i+1}),内部和外部的点有什么区别?(p_i-d)(p_{i+1}-d) 的外积是相反的。利用这个性质就可以规定一个方向来加减象限了。

    bool InPolygon_2(const vec p,const vector <vec> poy) {
    	int n=poy.size(),ret=0;
    	for(int i=0;i<n;++i) {
    		if(onSeg(poy[i],poy[(i+1)%n],p))
    			return 1;
    		double c=cross(poy[i]-p,poy[(i+1)%n]-p);
    		double d1=poy[i].y-p.y;
    		double d2=poy[(i+1)%n].y-p.y;
    		if(sgn(c)<0 and sgn(d1)<=0 and sgn(d2)>0) ++ret;
    		if(sgn(c)>0 and sgn(d2)<=0 and sgn(d1)>0) --ret;
    	}
    	return ret^0;
    }
    

    当然你还可以朴素地直接加象限,就像这样:

    int getD(const vec v) {
        if(v.x>=0 && v.y>=0) return 0;
        if(v.x>=0 && v.y<0) return 3;
        if(v.x<0 && v.y<0) return 2;
        return 1;
    }
    bool InPolygon_3(const vec p,const vector <vec> poy) {
    	int n=poy.size(),ret=0;
    	for(int i=0;i<n;++i) {
    		double c=cross(poy[i]-p,poy[(i+1)%n]-p);
    		if(sgn(c)==0 and sgn(dot(poy[i]-p,poy[(i+1)%n]-p))<=0)
    			return 1;
    		int d1=getD(poy[i]-p),d2=getD(poy[(i+1)%n]-p);
    		if(d2==(d1+1)%4) ++ret;
    		else if(d2==(d1+3)%4) --ret;
    		else if(d2==(d1+2)%4) {
    			if(sgn(c)>=0) ret+=2;
    			else ret-=2;
    		}
    	}
    	return ret^0;
    }
    

    2.4. 动态凸包

    #include <cstdio>
    #define print(x,y) write(x),putchar(y)
    
    template <class T>
    inline T read(const T sample) {
    	T x=0; char s; bool f=0;
    	while((s=getchar())>'9' or s<'0')
    		f|=(s=='-');
    	while(s>='0' and s<='9')
    		x=(x<<1)+(x<<3)+(s^48),
    		s=getchar();
    	return f?-x:x;
    }
    
    template <class T>
    inline void write(const T x) {
    	if(x<0) {
    		putchar('-'),write(-x);
    		return;
    	}
    	if(x>9) write(x/10);
    	putchar(x%10^48);
    } 
    
    #include <map>
    #define X first
    #define Y second
    using namespace std;
    typedef map <int,int> Map;
    typedef Map :: iterator It;
    
    Map up,down;
    
    long long cross(It o,It a,It b) {
    	return 1ll*(a->X-o->X)*(b->Y-o->Y)-
    			1ll*(a->Y-o->Y)*(b->X-o->X);
    }
    
    bool In(Map &t,int x,int y) {
    	if(t.empty()) return 0;
    	if(x<t.begin()->X or x>t.rbegin()->X)
    		return 0;
    	if(t.count(x)) return y>=t[x];
    	t[x]=y;
    	It it,i,j;
    	it=t.lower_bound(x);
    	i=j=it,--i,++j;
    	bool ret=cross(i,it,j)<=0;
    	t.erase(it);
    	return ret; 
    }
    
    void add(Map &t,int x,int y) {
    	if(In(t,x,y)) return;
    	It it,i,j;
    	t[x]=y;
    	it=t.lower_bound(x);
    	for(i=it,--i,j=i,--j;it!=t.begin() and i!=t.begin();i=j--)
    		if(cross(it,i,j)>=0) t.erase(i);
    		else break;
    	for(i=it,++i,j=i,++j;j!=t.end() and i!=t.end();i=j++)
    		if(cross(it,i,j)<=0) t.erase(i);
    		else break;
    }
    
    int main() {
    	int op,x,y;
    	for(int T=read(9);T;--T) {
    		op=read(9),x=read(9);
    		y=read(9);
    		if(op==1)
    			add(up,x,-y),add(down,x,y);
    		else if(In(up,x,-y) and In(down,x,y))
    			puts("YES");
    		else puts("NO");
    	}
    	return 0;
    }
    

    2.5. 最小圆覆盖

    戳这

    #include <bits/stdc++.h>
    using namespace std;
    
    template <class T>
    inline T read(const T sample) {
    	T x=0; char s; bool f=0;
    	while((s=getchar())>'9' or s<'0')
    		f|=(s=='-');
    	while(s>='0' and s<='9')
    		x=(x<<1)+(x<<3)+(s^48),
    		s=getchar();
    	return f?-x:x;
    }
    
    const double eps=1e-2,pi=acos(-1.0);
    inline int sgn(const double x) {
    	return x<-eps?-1:x>eps;
    }
    inline int dcmp(const double x,const double y) {
    	return sgn(x-y);
    } // if x>y, then return 1
    
    struct vec {
    	double x,y;
    	vec(const double X=0,const double Y=0):x(X),y(Y){}
    	
    	vec operator + (const vec t) const {
    		return vec(x+t.x,y+t.y);
        }
    	vec operator - (const vec t) const {
    		return vec(x-t.x,y-t.y);
        }
    	vec operator * (const double t) const {
    		return vec(x*t,y*t);
        }
    	vec operator / (const double t) const {
    		return vec(x/t,y/t);
        }
        bool operator < (const vec p) const {
    		int c=dcmp(x,p.x);
    		if(c) return c==-1;
    		return dcmp(y,p.y)==-1;
    	}
    	bool operator == (const vec p) const {
    		return !dcmp(x,p.x) && !dcmp(y,p.y);
        }
        friend double dot(const vec a,const vec b) {
            return a.x*b.x+a.y*b.y;
        }
    	friend double cross(const vec a,const vec b) {
            return a.x*b.y-a.y*b.x;
        }
        double len() const {
    		return sqrt(dot(*this,*this));
    	}
    	vec rot(const double alpha) const {
    		return vec(x*cos(alpha)-y*sin(alpha),x*sin(alpha)+y*cos(alpha));
    	}
    	vec normal() const { 
            double l=len();
            return vec(-y/l,x/l);
        }
    	friend double angle(const vec a,const vec b) {
       		return acos(dot(a,b)/a.len()/b.len());
    	}
    	friend double dis(const vec a,const vec b) {
    		return sqrt(dot(a-b,a-b));
    	}
    	void Read() {scanf("%lf %lf",&x,&y);}
    	void Print() {printf("%.2f %.2f ",x,y);}
    } O;
    
    struct line {
    	vec p,v;
    	line(const vec P,const vec V):p(P),v(V){}
    	
    	friend double dis(const line &l,const vec &p) {
            vec v=p-l.p;
            return fabs(cross(v,l.v))/(l.v).len();
        }
        friend bool onLine(const line &l,const vec &p) {
            vec v=p-l.p;
            return sgn(cross(v, l.v))==0;
        }
    };
    
    int n;
    double r;
    vec p[(int)1e5+5],o;
    
    vec GetPoint(const line &l1,const line &l2) {
    	vec c=l2.p-l1.p;
    	double t=cross(l2.v,c)/cross(l2.v,l1.v);
    	return l1.p+l1.v*t;
    }
    
    vec Circle(vec &a,vec &b,vec &c) {
    	return GetPoint(line((a+b)/2,(b-a).normal()),line((a+c)/2,(c-a).normal()));
    }
    
    int main() {
    	while(233) {
    		n=read(9);
    		if(!n) break;
    		for(int i=0;i<n;++i)	
    			p[i].Read();
    		srand(time(NULL));
    		random_shuffle(p,p+n);
    		for(int i=0;i<n;++i)
    			if(dcmp(dot(p[i]-o,p[i]-o),r)>0) {
    				o=p[i],r=0;
    				for(int j=0;j<i;++j)
    					if(dcmp(dot(p[j]-o,p[j]-o),r)>0) {
    						o=(p[i]+p[j])/2,r=dot(p[j]-o,p[j]-o);
    						for(int k=0;k<j;++k) 
    							if(dcmp(dot(p[k]-o,p[k]-o),r)>0) 
    								o=Circle(p[i],p[j],p[k]),
    								r=dot(p[i]-o,p[i]-o);
    					}
    			}
    		o.Print(),printf("%.2f
    ",sqrt(r));
    	}
    	return 0;
    }
    

    3. 板子

    #include <cmath>
    #include <cstdio>
    #include <vector>
    #include <cstdlib>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    template <class T>
    inline T read(const T sample) {
    	T x=0; char s; bool f=0;
    	while((s=getchar())>'9' or s<'0')
    		f|=(s=='-');
    	while(s>='0' and s<='9')
    		x=(x<<1)+(x<<3)+(s^48),
    		s=getchar();
    	return f?-x:x;
    }
    
    const double eps=1e-9,pi=acos(-1.0);
    inline int sgn(const double x) {
    	return x<-eps?-1:x>eps;
    }
    inline int dcmp(const double x,const double y) {
    	return sgn(x-y);
    } // if x>y, then return 1
    
    struct vec {
    	double x,y;
    	vec(const double X=0,const double Y=0):x(X),y(Y){}
    	
    	vec operator + (const vec t) const {return vec(x+t.x,y+t.y);} 
    	vec operator - (const vec t) const {return vec(x-t.x,y-t.y);}
    	vec operator * (const double t) const {return vec(x*t,y*t);}
    	vec operator / (const double t) const {return vec(x/t,y/t);}
        friend double dot(const vec a,const vec b) {return a.x*b.x+a.y*b.y;}
    	friend double cross(const vec a,const vec b) {return a.x*b.y-a.y*b.x;}
        double len() const {return sqrt(dot(*this,*this));}
        bool operator < (const vec p) const {
    		int c=dcmp(x,p.x);
    		if(c) return c==-1;
    		return dcmp(y,p.y)==-1;
    	}
    	bool operator == (const vec p) const {
    		return !dcmp(x,p.x) && !dcmp(y,p.y);
        }
    	vec rot(const double alpha) const {
    		return vec(x*cos(alpha)-y*sin(alpha),x*sin(alpha)+y*cos(alpha));
    	}
    	vec normal() const { 
            double l=len();
            return vec(-y/l,x/l);
        }
    	friend double angle(const vec a,const vec b) {
       		return acos(dot(a,b)/a.len()/b.len());
    	}
    	void Read() {scanf("%lf %lf",&x,&y);}
    	void Print() {printf("(%f,%f)
    ",x,y);}
    } O;
    
    struct line {
    	vec p,v;
    	line(const vec P,const vec V):p(P),v(V){}
    	
    	friend double dis(const line l,const vec p) {
            vec v=p-l.p;
            return fabs(cross(v,l.v))/(l.v).len();
        }
        friend bool onLine(const line l,const vec p) {
            vec v=p-l.p;
            return sgn(cross(v, l.v))==0;
        }
    };
    
    double dis(const vec l1,const vec l2,const vec p) {
        double d1=dot(p-l1,l2-l1),d2=dot(p-l2,l1-l2);
        if(sgn(d1)>=0 && sgn(d2)>=0) 
            return dis(line(l1,l2-l1),p);
        if(sgn(d1)<0) return (p-l1).len();
        return (p-l2).len();
    }
    
    bool onSeg(const vec l1,const vec l2,const vec p) {
        bool flag=onLine(line(l1,l2-l1),p);
        if(!flag) return 0;
        double d=dot(l1-p,l2-p);
        if(sgn(d)<=0) return 1;
        return 0;
    }
    
    bool SegIntersection(const vec a1,const vec a2,const vec b1,const vec b2) {
        double c1=cross(a2-a1,b1-a1),c2=cross(a2-a1,b2-a1);
        double c3=cross(b2-b1,a1-b1),c4=cross(b2-b1,a2-b1);
        int r1=sgn(c1)*sgn(c2),r2=sgn(c3)*sgn(c4);
        if(r1>0 || r2>0) return 0;
        if(r1==0 && r2==0) {
            if(onSeg(a1,a2,b1) || onSeg(a1,a2,b2))
                return 1;
            return 0;
        }
        return 1;
    }
    
    vec GetPoint(const line l1,const line l2) {
        vec c=l2.p-l1.p;
        if(fabs(cross(l1.v,l2.v))<eps) 
        	return vec(-1e9,-1e9);
        double t=cross(l2.v,c)/cross(l1.v,c);
        return l1.p+l1.v*t;
    }
    
    int getD(const vec v) {
        if(v.x>=0 && v.y>=0) return 0;
        if(v.x>=0 && v.y<0) return 3;
        if(v.x<0 && v.y<0) return 2;
        return 1;
    }
    
    double getArea(const vector <vec> poy) {
    	double ans=0;
    	int n=poy.size();
    	for(int i=0;i<n;++i)
    		ans+=cross(poy[i]-O,poy[(i+1)%n]-O);
    	return fabs(ans)/2;
    }
    
    bool inAngle(vec a,vec b,const vec p) {
    	if(sgn(cross(a,b))<0) swap(a,b);
    	return sgn(cross(a,p))>=0 and sgn(cross(b,p))<=0;
    }
    
    bool InPolygon_1(const vec p,const vector <vec> poy) {
    	int n=poy.size();
    	double r=(rand()/double(RAND_MAX)-0.5)*2*pi;
    	vec v(cos(r),sin(r));
    	bool ret=0; 
    	for(int i=0;i<n;++i) {
    		if(onSeg(poy[i],poy[(i+1)%n],p))
    			return 1;
    		ret=inAngle(poy[i]-p,poy[(i+1)%n]-p,v)?(!ret):ret;
    	}
    	return ret;
    }
    
    bool InPolygon_2(const vec p,const vector <vec> poy) {
    	int n=poy.size(),ret=0;
    	for(int i=0;i<n;++i) {
    		if(onSeg(poy[i],poy[(i+1)%n],p))
    			return 1;
    		double c=cross(poy[i]-p,poy[(i+1)%n]-p);
    		double d1=poy[i].y-p.y;
    		double d2=poy[(i+1)%n].y-p.y;
    		if(sgn(c)<0 and sgn(d1)<=0 and sgn(d2)>0) ++ret;
    		if(sgn(c)>0 and sgn(d2)<=0 and sgn(d1)>0) --ret;
    	}
    	return ret^0;
    }
    
    bool InPolygon_3(const vec p,const vector <vec> poy) {
    	int n=poy.size(),ret=0;
    	for(int i=0;i<n;++i) {
    		double c=cross(poy[i]-p,poy[(i+1)%n]-p);
    		if(sgn(c)==0 and sgn(dot(poy[i]-p,poy[(i+1)%n]-p))<=0)
    			return 1;
    		int d1=getD(poy[i]-p),d2=getD(poy[(i+1)%n]-p);
    		if(d2==(d1+1)%4) ++ret;
    		else if(d2==(d1+3)%4) --ret;
    		else if(d2==(d1+2)%4) {
    			if(sgn(c)>=0) ret+=2;
    			else ret-=2;
    		}
    	}
    	return ret^0;
    }
    
    vec getCentre(const vector <vec> poy) {
        double area=0; int n=poy.size();
        vec ret(0,0);
        for(int i=0;i<n;++i) {
            double t=cross(poy[i]-O,poy[(i+1)%n]-O);
            area+=t;
            ret.x+=(poy[i].x+poy[(i+1)%n].x)*t;
            ret.y+=(poy[i].y+poy[(i+1)%n].y)*t;
        }
        ret.x/=3,ret.x/=area;
        ret.y/=3,ret.y/=area;
        return ret;
    }
    
    vec cox[1000];
    int tp;
    void Andrew(const vector <vec> poy) {
    	int n=poy.size();
    	vec t[1000];
    	for(int i=0;i<n;++i) t[i]=poy[i];
    	sort(t,t+n);
    	tp=0;
    	for(int i=0;i<n;++i) {
    		while(tp>1 and sgn(cross(cox[tp-1]-cox[tp-2],t[i]-cox[tp-1]))<=0)
    			--tp;
    		cox[tp++]=t[i];
    	}
    	int lim=tp;
    	for(int i=n-2;i>=0;--i) {
    		while(tp>lim and sgn(cross(cox[tp-1]-cox[tp-2],t[i]-cox[tp-1]))<=0)
    			--tp;
    		cox[tp++]=t[i];
    	}
    	if(n>1) --tp;
    }
    
    // 计算圆的交的面积
    double calc(const vec &o,const double r1,const int i) {
    	double d=sqrt(dot(o-a[i],o-a[i]));
    	if(dcmp(d,r1+r[i])>0) return 0;
    	if(dcmp(d,fabs(r1-r[i]))<0) 
    		return min(r1,r[i])*min(r1,r[i])*pi;
    	double a1=acos((r1*r1+d*d-r[i]*r[i])/(r1*d*2));
    	double a2=acos((r[i]*r[i]+d*d-r1*r1)/(r[i]*d*2));
    	return a1*r1*r1+a2*r[i]*r[i]-sin(a1)*r1*d;
    }
    
    int main() {
    	
    	return 0;
    }
    
    /*
    point line_intersection(point a,point a0,point b,point b0)  
    {  
        double a1,b1,c1,a2,b2,c2;  
        a1 = a.y - a0.y;  
        b1 = a0.x - a.x;  
        c1 = cross(a,a0);  
        a2 = b.y - b0.y;  
        b2 = b0.x - b.x;  
        c2 = cross(b,b0);  
        double d = a1 * b2 - a2 * b1;  
        return point((b1 * c2 - b2 * c1) / d,(c1 * a2 - c2 * a1) / d);  
    }
    */
    

    4. 一些题

    例 1. ( ext{Segments}):给定 (nle 100) 条线段,判断是否存在一条直线使得所有线段在直线上的投影有交点。

    问题可以转化为是否存在一条直线经过所有线段,这条直线与我们要求的直线垂直。一定存在这样的直线经过线段的端点,所以可以直接枚举。

    例 2. ( ext{That Nice Euler Circuit})

    题目有一些比较重要的性质:

    • (p_i eq p_{i-1})
    • 欧拉机器绝对不会画出任何与其他已经画出的线重叠的线。然而,这两条线也可能相交。

    欧拉定理:(G) 为任意的连通的平面图,则 (v-e+f=2)(v)(G) 的顶点数,(e)(G) 的边数,(f)(G) 的面数。

    首先枚举给出线段求出所有交点,不过交点会有重合,用 std::unique() 即可。枚举所有交点 (i),给出线段 (j),如果 (i)(j) 上(不包含端点)就增加一条边。

    为什么?题目保证没有重叠的线,所以交点不会划分已划分的线段。

    例 3. ( ext{CodeForces - 1C Ancient Berland Circus})

    首先多边形一定在三角形的外接圆上,否则三个端点不可能和多边形端点重合。其次应使多边形端点尽量少,从而最小化面积。

    利用正弦定理可知 (r=frac{abc}{4S})。三角形每条边对应的圆心角度数是 (arccos (frac{2r^2-l^2}{2r^2}))

    将圆心角度数取 (gcd) 可以最大化中心角,从而最小化多边形顶点数。第三个圆心角可以直接取 (2pi-alpha-eta) 因为它要么是 (alpha+eta) 要么是 (2pi-alpha-eta)

    例 4. ( ext{UVA - 10256 The Great Divide})

    判断两个凸包是否相交。

    • 是否有相交线段。特判凸包退化成线段的情况。
    • 点是否在另一凸包内。这是为了判断凸包相互包含的情况。
  • 相关阅读:
    SqlLikeAttribute 特性增加 左、右Like实现
    MySql高效分页SQL
    ConcurrentQueue对列的基本使用方式
    第一次
    kubeadm搭建高可用k8s平台(多master)
    prometheus监控
    pyecharts地图中显示地名
    anaconda安装及使用
    Python的pyecharts安装
    安装MY SQL详细步骤
  • 原文地址:https://www.cnblogs.com/AWhiteWall/p/14090379.html
Copyright © 2011-2022 走看看