基础

  1. 点(向量):看作 1×21\times2 的矩阵,点积意为投影,叉积意为面积,正负按右手定则。

  2. 点排序:按 pair 排;线排序:按极角 atan2(y,x)atan2(y,x) 排,平行则上面排前面。

     bool operator <(Line &a)
     {
         double x=angle(),y=a.angle();
         if(abs(x-y)<eps) return (v^(a.P-P))<0;
         return x<y;
     }
    
  3. 两线交点:平行则不交,端点出发按比例移动到交点,比例按叉积算面积比例来算。

    Point inter(const Line &a,const Line &b)
    {
        if(abs(a.v^b.v)<eps) return {7-p,p+9};    //not_inter
        return a.P+a.v*(((b.P-a.P)^b.v)/(a.v^b.v));
    }
    
  4. 两线夹角:点积定义即可:ab=abcosθa·b=|a||b|\cos\theta

  5. 点/向量顺时针旋转:[xy]×[cosθsinθsinθcosθ]\begin{bmatrix} x & y \end{bmatrix}\times\begin{bmatrix} \cos\theta & -\sin\theta\\ \sin\theta & \cos\theta \end{bmatrix}

  6. 点到直线的距离:叉积算面积除以底;点到线段的距离:特判点,点积看是否在两端点外,规约到直线。

    double point_to_seg(const Point &x,const Point &a,const Point &b)
    {
        if(a==b) return (x-a).mo();
        Vector v1=b-a,v2=x-a,v3=x-b;
        if(v1*v2<0) return v2.mo();
        if(v1*v3>0) return v3.mo();
        return point_to_line(x, a, b);
    }
    
  7. 点在直线上:与两端点连线叉积为 00 ;点在线段上:且内向连线点积 0\leq0 .

凸包

  1. 求凸包:将点排序,依次往里加点同时维护凸性即可,正反做一遍,凸性用叉积判即可,平的就看是否新的更短即可,得到凸包逆时针序点集。

     bool tu(const Point &a,const Point &b,const Point &c)
     {
         double pos=(a-c)^(b-c);
         if(abs(pos)>eps) return pos<0;
         return (a-c).mo()<(b-c).mo()+eps;
     }
    int n;Point P[N],h[N<<1];int tail;
    void hull()
    {
        sort(P+1,P+n+1);
        h[++tail]=P[1],h[++tail]=P[2];
        for(rint i=3;i<=n;h[++tail]=P[i],i++)
            while(tail>1&&!tu(P[i],h[tail],h[tail-1])) tail--;
        h[++tail]=P[n-1];
        for(rint i=n-2;i;h[++tail]=P[i],i--)
            while(tail>1&&!tu(P[i],h[tail],h[tail-1])) tail--;
        //h[tail]==h[1]
    }
    
  2. 旋转卡壳:求凸包直径,发现具有单调性和单峰性,枚举边同时单指针跟着走即可,用叉积判距离即可。

    double hull_len()
    {
        if(tail<=3) return (h[1]-h[tail-1]).mo();
        double ans=0;
        for(rint i=1,j=3;i<tail;i++)
        {
            while(((h[i+1]-h[i])^(h[j]-h[i]))<((h[i+1]-h[i])^(h[j+1]-h[i]))) j=(j==tail-1?1:j+1);
            ans=max(ans,max((h[j]-h[i]).mo(),(h[j]-h[i+1]).mo()));
        }
        return ans;
    }
    
  3. 闵可夫斯基和:求两凸包点集之和形成的凸包,容易发现新凸包的边由原两凸包边组成,找到左下点然后一步步贪心加边即可。

    int n,m,q,num1,num2;
    Point h1[N<<1],h2[N<<1],H[N<<2];int cnt;
    
    void minkfusji()
    {
    
       	H[cnt=1]=h1[1]+h2[1];
      	int now1=1,now2=1;
      	while(now1<num1&&now2<num2)
       	{
       		if(((h1[now1+1]-h1[now1])^(h2[now2+1]-h2[now2]))>0)
       		H[cnt+1]=H[cnt]+h1[now1+1]-h1[now1],now1++,cnt++;
       		else
       		H[cnt+1]=H[cnt]+h2[now2+1]-h2[now2],now2++,cnt++;
       	}
     	while(now1<num1) H[cnt+1]=H[cnt]+h1[now1+1]-h1[now1],now1++,cnt++;
      	while(now2<num2) H[cnt+1]=H[cnt]+h2[now2+1]-h2[now2],now2++,cnt++;
    }
    
  4. 动态凸包:增量法,set 维护上下凸壳点集,势能分析复杂度是 O(nlog)O(n\log) ,二分找推平的区间端点即可。

  5. 三维凸包:考虑 O(n2)O(n^2) 增量法,每次加点暴力枚举所有面看有没有被看到即可,要扰动点避免四点共面。

    mt19937 rd(time(0));
    struct Point{
        double x,y,z;
        Point operator-(const Point &a)const{return {x-a.x,y-a.y,z-a.z};}
        double operator*(const Point &a)const{return x*a.x+y*a.y+z*a.z;}
        Point operator^(const Point &a)const{return {y*a.z-z*a.y,z*a.x-x*a.z,x*a.y-y*a.x};}
        double mo(){return sqrt(x*x+y*y+z*z);}
        void shake(){x+=rd(),y+=rd(),z+=rd();}
    }P[N];
    struct Plane{
        int e[3];
        Vector fa(){return (P[e[1]]-P[e[0]])^(P[e[2]]-P[e[0]]);}
        double area(){return fa().mo()/2;}
        bool above(const Point &a){return (a-P[e[0]])*fa()>=0;}
    }pl[N<<1],res[N<<1];
    int n,m;
    bool use[N][N];
    void hull()
    {
        pl[++m]={1,2,3},pl[++m]={3,2,1};
        for(rint i=4;i<=n;i++)
        {
            int cnt=0;
            for(rint j=1;j<=m;j++)
            {
                bool in=pl[j].above(P[i]);
                if(!in) res[++cnt]=pl[j];
                for(rint k=0;k<3;k++) use[pl[j].e[k]][pl[j].e[(k+1)%3]]=in;
            }
            for(rint j=1;j<=m;j++)
            for(rint k=0;k<3;k++)
            {
                int x=pl[j].e[k],y=pl[j].e[(k+1)%3];
                if(use[x][y]&&!use[y][x]) res[++cnt]={x,y,i};
            }
            m=cnt;
            for(rint j=1;j<=m;j++) pl[j]=res[j];
        }
    }
    double hull_area()
    {
        hull();
        double ans=0;
        for(rint i=1;i<=m;i++) ans+=pl[i].area();
        return ans;
    }
    

半平面交

  1. 求半平面交:将线排序,维护有效线的双端队列,依次往里加线,对于尾首维护交点在右,加完了再将尾首对起来维护,得到左半平面交,凸包点即为相邻两线交点,不封闭可以在外面框一层边界。

    Line line[N];int n;
    int q[N],hh=1,tt;
    Point cut[N];int num;
    bool isright(const Line &a,const Line &b,const Line &c)
    {
        Point P=inter(b,c);
        return (a.v^(P-a.P))<-eps;
    }
    void halfcut()
    {
        sort(line+1,line+n+1);
        for(rint i=1;i<=n;i++)
        {
            while(hh<tt&&isright(line[i],line[q[tt]],line[q[tt-1]])) tt--;
            while(hh<tt&&isright(line[i],line[q[hh]],line[q[hh+1]])) hh++;      
            q[++tt]=i;
        }
        while(hh<tt&&isright(line[q[hh]],line[q[tt]],line[q[tt-1]])) tt--;
        while(hh<tt&&isright(line[q[tt]],line[q[hh]],line[q[hh+1]])) hh++;
        q[++tt]=q[hh];
        for(rint i=hh;i<tt;i++) cut[++num]=inter(line[q[i]],line[q[i+1]]);
    }
    

  1. 三点定圆:即求外心,求两角平分线交点即可。

    Circle get_circle(const Point &a,const Point &b,const Point &c)
    {
        Line ab={(a+b)*0.5,rotate(b-a,Pi/2)},
        bc={(b+c)*0.5,rotate(c-b,Pi/2)};
        Point P=inter(ab,bc);
        return {P,dis(P,a)};
    }
    
  2. 最小圆覆盖:随机增量法,每次加入的点若不在圆内则新圆必过该点,循环三层即可,将点集打乱后每次暴力重来复杂度是 O(n)O(n) .

    int n;Point P[N];
    Circle min_circle()
    {
        mt19937 rd(time(0));
        shuffle(P+1,P+n+1,rd);
        Circle c={P[1],0};
        for(rint i=2;i<=n;i++)
        if(dis(c.P,P[i])-c.r>eps)
        {
            c={P[i],0};
            for(rint j=1;j<i;j++)
            if(dis(c.P,P[j])-c.r>eps)
            {
                c={(P[i]+P[j])*0.5,dis(P[i],P[j])/2};
                for(rint k=1;k<j;k++)
                if(dis(c.P,P[k])-c.r>eps)
                     c=get_circle(P[i],P[j],P[k]);
            }
        }
        return c;
    }
    
  3. 圆反演:

    1. 圆内外的点一一映射,PPPP' 互为反演关系 \Leftrightarrow OPOP=R2|OP|·|OP'|=R^2OPPOPP' 共线。
    2. 不过反演圆心的圆反演后仍为圆,r=12(1OAr1OA+r)R2r'=\frac 1 2 (\frac 1 {|OA|-r}-\frac 1 {|OA|+r})R^2,过反演圆心的点反演后为两交点连成的直线。
    3. 两图形相切其对应的反演图形也相切。
    4. 应用:求过两圆外一点且与两圆相切的所有的圆。考虑以该点为反演圆心做圆反演,半径任意,那么符合条件的圆的反演为与两圆反演后的两圆的公切线,动手做点辅助线就可以推出来了。

复杂图形面积并

  1. 辛普森积分法:大致对于图形划分边界,对于每个边界内部用辛普森积分法计算,不断往下递归直到左半右半估算之和与总体估算误差很小,估算公式为 len×(f(l)+4f(mid)+f(r))/6len\times(f(l)+4f(mid)+f(r))/6 ,可以要求递归层数。
   double f(double x)
   {
   		//暴力计算横坐标x被覆盖的长度和
   }
   double cal(double l,double r)
   {

  	 	return (r-l)*(f(l)+4*f((r+l)/2)+f(r))/6;
   }
  	double sim(double l,double r,double ans,double Eps=eps,int dep=1)
   {
  	 	double mid=(l+r)/2,lans=cal(l,mid),rans=cal(mid,r);
  	 	if(abs(lans+rans-ans)<=15*Eps&&dep<0) return lans+rans+(lans+rans-ans)/15;
   		return sim(l,mid,lans,Eps/2,dep-1)+sim(mid,r,rans,Eps/2,dep-1);
   }
   double work()
   {
 	  	double ans=0;
  	 	for(rint i=1;i<n;i++) ans+=sim(step[i],step[i+1],cal(step[i],step[i+1]));
   		return ans;
   }

三角剖分

  1. 凸包:随便选一个点作为中心,把所有边作为底与该点连边就可以了。
  2. 任意多边形:若可以负面积则按凸包的做就可以了,否则 O(n2)O(n^2) 每次暴力找一个凸角剖分递归下去,不会更优复杂度的做法。

根据占卜 你似乎还会再次造访这里
明日 听到正午钟声的时候