zoukankan      html  css  js  c++  java
  • 计算几何总结

    凸包

    1、找到最左面的点,如有多个,取最下面的的点。
    注:这一步是为了极角排序时方便直接叉积。
    2、以这个点为原点,将所有点按与x轴正半轴的夹角为第一关键字(从,顺时针),到原点距离为第二关键字(从)排序。

    比较的代码:

    int left(SVe a,SVe b)
    {
    	return sgn(a*b);
    }
    int cmp(const void*a,const void *b)
    {
    	int t=left(*(SPt*)a-o,*(SPt*)b-o);
    	if(t!=0)
    		return t;
    	return sgn(getlen(*(SPt*)a-o)-getlen(*(SPt*)b-o));
    }
    

    3、用栈维护,保证后一个向量在前一个的右面。

    o=sz[0];
    for(int i=1;i<n;i++)
    {
    	if(sgn(sz[i].x-o.x)==-1||(sgn(sz[i].x-o.x)==0&&sgn(sz[i].y-o.y)==-1))
    		o=sz[i];
    }
    qsort(sz,n,sizeof(SPt),cmp);
    st[tp++]=o;
    for(int i=1;i<n;i++)
    {
    	while(tp>=2&&left(st[tp-1]-st[tp-2],sz[i]-st[tp-1])==1)
    		tp-=1;
    	st[tp++]=sz[i];
    }
    

    带曲线的凸包:[SHOI2012]信用卡凸包

    通过平移,可以发现:所有的曲线正好形成一个圆,所有直线分别向内平移可以形成一个完整凸包。
    因此,求出所有圆心组成的凸包即可。
    再加上一个圆就是答案。

    极角排序

    极角:

    atan2(y,x);
    

    从x轴负半轴开始,时针为增加,从(-π)(π)。其中上是(π)
    处理时可以类似环的处理方法,将范围变为2倍,从(-π到3π)

    使用(atan2)求极角通常会有精度损失。
    另一种方法就是使用叉积。但是注意叉积对于转角超过(180)度时会错。
    解决方案:比较时,若分别在(x)轴两侧,直接判断。
    否则,使用叉积。因为这是可以保证转角严格小于(180)度。
    代码:

    int xix(SVe a)
    {
        if(a.y<0||(a.y==0&&a.x<0))
            return 0;
        return 1;
    }
    int cmpj(SVe a,SVe b)
    {
        int xa=xix(a),xb=xix(b);
        if(xa!=xb)return xa-xb;
        return cross(a,b);
    }
    

    旋转卡壳

    可以求凸包上的最值等问题。
    例如:最远点对,最小外界矩形,最大四边形面积等。

    对于最远点对,就是先枚举一条边,然后用与其平行的直线切割凸包的另一面。
    如果按照顺时针枚举这条边,那么另一个点也是顺时针转动的,可以使用双指针。

    平行的切线可以使用叉积判断(面积最大)。

    最远点对

    double getmax(int n,SPt sz[50010])
    {
    	double ma=0;
    	for(int i=0,j=1/*注意此处*/;i<n;i++)
    	{
    		while(mianj(sz[(j+1)%n],sz[i],sz[(i+1)%n])>mianj(sz[j],sz[i],sz[(i+1)%n]))
    			j=(j+1)%n;
    		double t=getlen(sz[i]-sz[j]);
    		if(t>ma)ma=t;
    		t=getlen(sz[(i+1)%n]-sz[(j+1)%n]);//要判断两种情况
    		if(t>ma)ma=t;
    	}
    	return ma;
    }
    

    最小外接矩形

    平行的边和之前一样。
    与枚举的边垂直的边可以使用点积判断(最大化点的投影到端点的距离)。
    注意只能垂直于枚举边,不能用对面的边(对面的与凸包仅有一个交点)。

    double getans(int n,SPt sz[50010],SPt dd[5])
    {
    	double ma=1e100;sz[n]=sz[0];
    	for(int i=0,j=1,a=1,b=1;i<n;i++)
    	{
    		while(mianj(sz[j+1],sz[i],sz[i+1])>=mianj(sz[j],sz[i],sz[i+1]))
    			j=(j+1)%n;
    		while(dianj(sz[a+1]-sz[i],sz[i+1]-sz[i])>=dianj(sz[a]-sz[i],sz[i+1]-sz[i]))
    			a=(a+1)%n;
    		if(i==0)b=j;
    		while(dianj(sz[b+1]-sz[i],sz[i]-sz[i+1])>=dianj(sz[b]-sz[i],sz[i]-sz[i+1]))
    			b=(b+1)%n;
    		SPt A=getH(sz[i],sz[i+1]-sz[i],sz[a]);
    		SPt B=getH(A,getcz(sz[i+1]-sz[i]),sz[j]);
    		SPt C=getH(B,getcz(A-B),sz[b]);
    		SPt D=getH(sz[i],sz[i+1]-sz[i],sz[b]);
    		double S=fabs(mianj(A,B,D));
    		if(S<ma)
    		{
    			dd[0]=D;dd[1]=C;
    			dd[2]=B;dd[3]=A;
    			ma=S;
    		}
    	}
    	return ma;
    }
    

    最小外接圆

    有如下步骤:
    首先,随机打乱,防止被卡。
    将点按照1~n的顺序依次添加。
    若当前点i在圆内,则跳过这个点。
    若i不在圆内,那么这个点一定在外接圆上,以这个点为圆心,半径为0作为新圆。
    枚举1~i-1,若j在圆内则跳过,否则以i,j为直径作为新圆。
    枚举1~j-1,若k在圆内则跳过,否则用i,j,k确定一个新圆。
    其实就是暴力,但期望(O(n))

    代码

    SCr c(sz[0],0);
    for(int i=1;i<n;i++)
    {
    	if(inc(c,sz[i]))
    		continue;
    	c=SCr(sz[i],0);
    	for(int j=0;j<i;j++)
    	{
    		if(inc(c,sz[j]))
    			continue;
    		c=SCr(mid(sz[i],sz[j]),getle2(sz[i]-sz[j])/4);//为了方便,用的是平方
    		for(int k=0;k<j;k++)
    		{
    			if(!inc(c,sz[k]))
    				c=getcr(sz[i],sz[j],sz[k]);
    		}
    	}
    }
    

    半平面交

    就是给出若干半平面,求交集。(一定是凸多边形)
    用途很多,比如:

    求一些凸多边形的交集 [CQOI2006]凸多边形
    求二元一次不等式组的解集 [SCOI2015]小凸想跑步
    求多边形的核:即多边形内的一个点集,点集内任意一点与多边形边上任意一点的连线都在多边形内。

    求法(假设在直线左面):

    首先,按照极角从排序,平行的线,靠左的排前面。

    struct SLi
    {
    	SPt s,t;double ji;
    	SLi(){}
    	SLi(SPt S,SPt T)
    	{
    		s=S;t=T;
    		ji=atan2(t.y-s.y,t.x-s.x);
    	}
    };
    int cmp(const void*a2,const void*b2)
    {
    	SLi a=*(SLi*)a2,b=*(SLi*)b2;
    	int t=sgn(a.ji-b.ji);
    	if(t!=0)return t;
    	return right(b,a.s);
    }
    

    然后,依次添加,和之前平行的舍去。
    维护两个双端队列,一个保存直线,一个保存交点。
    添加分三步:
    1、从队,把交点在当前直线右面的交点去掉,同时也去掉队直线。
    2、从队,把交点在当前直线右面的交点去掉,同时也去掉队直线。
    3、添加当前直线,添加该直线和队列中上一条直线的交点。

    最后,用队首直线排除队尾直线,方法同之前。

    代码

    double bpmj(SLi sz[200010],int n)
    {
    	qsort(sz,n,sizeof(SLi),cmp);
    	int he=0,ta=0;
    	for(int i=0;i<n;i++)
    	{
    		if(i>0&&pingx(sz[i],sz[i-1]))
    			continue;
    		while(he+1<ta&&right(sz[i],jd[ta-1])==1)
    			ta-=1;
    		while(he+1<ta&&right(sz[i],jd[he+1])==1)
    			he+=1;
    		if(he<ta)
    			jd[ta]=jiaod(sz[i],dl[ta-1]);
    		dl[ta++]=sz[i];
    	}
    	while(he+1<ta&&right(dl[he],jd[ta-1])==1)
    		ta-=1;
    	jd[he]=jiaod(dl[he],dl[ta-1]);
    	jd[ta]=jd[he];
    	double ans=0;
    	for(int i=he;i<ta;i++)
    		ans+=jd[i]*jd[i+1];
    	return ans/2;
    }
    

    对于不等式(ax+by+cgeq0),也可以转化为直线。

    SLi::SLi(ll a,ll b,ll c)
    {
    	SPt s0,t0;
    	if(b==0)
    	{
    		s0=SPt(double(-c)/a,0);
    		t0=SPt(double(-c)/a,1);
    	}
    	else
    	{
    		s0=SPt(0,double(-c)/b);
    		t0=SPt(1,double(-c-a)/b);
    	}
    	SPt jc=s0;
    	if(a>0)jc.x+=1;
    	else jc.x-=1;
    	if(b>0)jc.y+=1;
    	else jc.y-=1;
            //似乎是糟糕的做法? --> 我也不知道是啥了,降智了
    	if(right(SLi(s0,t0),jc)==-1)
    		*this=SLi(s0,t0);
    	else
    		*this=SLi(t0,s0);
    }
    

    Simpson积分

    用途:求面积时,若组合图形不易分割,但可以算出每个横坐标上的纵坐标值,就可用此方法。
    例题 [NOI2005]月下柠檬树

    基本思想就是把复杂的函数近似成二次函数。
    先考虑二次函数的积分:

    这样子直接计算我们发现误差非常大,毕竟原图像可能不能很好的用二次函数逼近
    所以,有自适应 Simpson 积分:

    发现当前区间分成两份和不分相差不大时,直接返回,否则分两半递归。

    代码

    double sim(double a,double b)
    {
    	return (b-a)*(F(a)+F(b)+4*F((a+b)/2))/6;
    }
    double asr(double a,double b,double z)
    {
    	double m=(a+b)/2,cl=sim(a,m),cr=sim(m,b);
    	if(fabs(cl+cr-z)<eps)
    		return z;
    	return asr(a,m,cl)+asr(m,b,cr);
    }
    

    其他常用操作

    向量运算

    struct SPt
    {
    	double x,y;
    	SPt(){}
    	SPt(double X,double Y)
    	{
    		x=X;y=Y;
    	}
    };
    typedef SPt SVe;
    int sgn(double t)//符号
    {	
    	if(t>eps)return 1;
    	else if(t<-eps)
    		return -1;
    	else return 0;
    }
    SVe operator-(SPt a,SPt b)
    {
    	return SVe(a.x-b.x,a.y-b.y);
    }
    SVe operator*(SVe a,double b)//数乘
    {
    	return SVe(a.x*b,a.y*b);
    }
    SPt operator+(SPt a,SVe b)
    {
    	return SPt(a.x+b.x,a.y+b.y);
    }
    double operator*(SVe a,SVe b)//叉积
    {
    	return a.x*b.y-a.y*b.x;
    }
    double dianj(SPt a,SPt b)//点积
    {
    	return a.x*b.x+a.y*b.y;
    }
    SVe getcz(SVe a)//垂直向量
    {
    	return SVe(a.y,-a.x);
    }
    double getlen(SVe a)
    {
    	return sqrt(a.x*a.x+a.y*a.y);
    }
    

    求交点

    SPt jiaod(SPt a,SVe va,SPt b,SVe vb)
    {
    	double t=((b-a)*vb)/(va*vb);
    	return a+va*t;
    }
    

    求垂足(投影)

    SPt getH(SPt O,SVe a,SPt N)
    {
    	double t=dianj(a,N-O)/dianj(a,a);
    	SPt M;
    	M.x=O.x+a.x*t;M.y=O.y+a.y*t;
    	return M;
    }
    

    旋转

    SVe rotate(SVe a,double r)
    {
    	double tx=cos(r),ty=sin(r);
    	SVe rt;
    	rt.x=a.x*tx-a.y*ty;
    	rt.y=a.x*ty+a.y*tx;
    	return rt;
    }
    

    三点确定一个圆

    struct SCr
    {
    	SPt o;
    	double r;
    	SCr(){}
    	SCr(SPt O,double R)
    	{
    		o=O;r=R;
    	}
    };
    SCr getcr(SPt a,SPt b,SPt c)
    {
    	SPt p1=mid(a,b),p2=mid(b,c);
    	SPt o=jiaod(p1,getcz(p1-a),p2,getcz(p2-b));
    	return SCr(o,getle2(o-a));
    }
    

    求公切线(一条)

    利用相似

    for(int i=0;i<n;i++)
    {
    	double d=x[i+1]-x[i],a=r[i+1]-r[i],c=sqrt(d*d-a*a);
    	double x0=x[i]-r[i]/d*a,y0=r[i]/d*c;
    	double x1=x[i+1]-r[i+1]/d*a,y1=r[i+1]/d*c;
    	k[i]=(y1-y0)/(x1-x0);
    	b[i]=y0-k[i]*x0;
    }
    

    求出所有公切线

    以一个圆心为原点,将坐标进行变换,另一个圆心放到x轴上。
    就可以类似上一个的方法了,还是相似。
    最后别忘变换回去。

    #include <stdio.h> 
    #include <stdlib.h> 
    #include <math.h> 
    #define eps 1e-6 
    struct SPt {
    	double x,y;
    	SPt() {}
    	SPt(double X, double Y) {
    		x = X;y = Y;
    	}
    };
    SPt rotate(SPt a, double co, double si) {
    	SPt rt;
    	rt.x = a.x * co - a.y * si;
    	rt.y = a.x * si + a.y * co;
    	return rt;
    }
    struct SLi {
    	SPt s,t;
    };
    void geta2(double r1, double d, double r2, SLi & l1, SLi & l2) {
    	double t = r2 - r1,l = sqrt(d * d - t * t);
    	double si = l / d,co = t / d;
    	l1.s = SPt( - r1 * co, r1 * si);
    	l1.t = SPt(d - r2 * co, r2 * si);
    	l2.s = SPt( - r1 * co, -r1 * si);
    	l2.t = SPt(d - r2 * co, -r2 * si);
    }
    void getb2(double r1, double d, double r2, SLi & l1, SLi & l2) {
    	double t = r2 + r1,l = sqrt(d * d - t * t);
    	double si = l / d,co = t / d;
    	l1.s = SPt(r1 * co, -r1 * si);
    	l1.t = SPt(d - r2 * co, r2 * si);
    	l2.s = SPt(r1 * co, r1 * si);
    	l2.t = SPt(d - r2 * co, -r2 * si);
    }
    int getjd(double r1, double d, double r2, SLi li[5]) {
    	if (fabs(d) < eps && fabs(r1 - r2) < eps) return - 1;
    	double a1 = -r1,b1 = r1,a2 = d - r2,b2 = d + r2;
    	if (a1 - a2 > eps || b1 - b2 > eps) return 0;
    	if (fabs(a1 - a2) < eps) {
    		li[0].s = li[0].t = SPt( - r1, 0);
    		return 1;
    	}
    	if (fabs(b1 - b2) < eps) {
    		li[0].s = li[0].t = SPt(r1, 0);
    		return 1;
    	}
    	geta2(r1, d, r2, li[0], li[1]);
    	if (b1 - a2 > eps) return 2;
    	if (fabs(b1 - a2) < eps) {
    		li[2].s = li[2].t = SPt(r1, 0);
    		return 3;
    	}
    	getb2(r1, d, r2, li[2], li[3]);
    	return 4;
    }
    SLi li[5];
    int sgn(double x) {
    	if (x > eps) return 1;
    	if (x < -eps) return - 1;
    	return 0;
    }
    int cmp(const void * a, const void * b) {
    	int x = sgn(((SLi * ) a) ->s.x - ((SLi * ) b) ->s.x);
    	int y = sgn(((SLi * ) a) ->s.y - ((SLi * ) b) ->s.y);
    	if (x != 0) return x;
    	return y;
    }
    int main() {
    	while (1) {
    		double x1,y1,r1,x2,y2,r2;
    		scanf("%lf%lf%lf%lf%lf%lf", &x1, &y1, &r1, &x2, &y2, &r2);
    		if (fabs(r1) < eps) break;
    		double d = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
    		double a = x2 - x1,b = y2 - y1,co = a / d,si = b / d;
    		int n = getjd(r1, d, r2, li);
    		printf("%d
    ", n);
    		if (n == -1) n = 0;
    		for (int i = 0; i < n; i++) {
    			li[i].s = rotate(li[i].s, co, si);
    			li[i].t = rotate(li[i].t, co, si);
    		}
    		qsort(li, n, sizeof(SLi), cmp);
    		for (int i = 0; i < n; i++) {
    			double a = li[i].s.x - li[i].t.x;
    			double b = li[i].s.y - li[i].t.y;
    			printf("%.5lf %.5lf %.5lf %.5lf %.5lf
    ", li[i].s.x + x1, li[i].s.y + y1, li[i].t.x + x1, li[i].t.y + y1, sqrt(a * a + b * b));
    		}
    	}
    	return 0;
    }
    

    注意事项

    1、若比较的符号带等于,则需要使用eps判断。
    2、注意输出-0的情况。

    void output(double x)
    {
    	if(fabs(x)<eps)
    		x=0;
    	printf("%.5lf ",x);
    }
    

    3、若出现奇怪错误,可能是数组开小,eps过大/过小。

    总之,注意精度。
    万不得已可以手写分数类提高精度。

  • 相关阅读:
    hihoCoder #1176 : 欧拉路·一 (简单)
    228 Summary Ranges 汇总区间
    227 Basic Calculator II 基本计算器II
    226 Invert Binary Tree 翻转二叉树
    225 Implement Stack using Queues 队列实现栈
    224 Basic Calculator 基本计算器
    223 Rectangle Area 矩形面积
    222 Count Complete Tree Nodes 完全二叉树的节点个数
    221 Maximal Square 最大正方形
    220 Contains Duplicate III 存在重复 III
  • 原文地址:https://www.cnblogs.com/lnzwz/p/11976181.html
Copyright © 2011-2022 走看看