计算几何【模板】

· · 个人记录

此博客是为了存放模板开的blog。


#include <bits/stdc++.h>

using namespace std;

#define print_v(a) printf("%.2lf %.2lf\n",a.x,a.y)
#define print_l(x) putchar('\n'),print_v(x.a),print_v(x.b)
#define SZ(x) ((int)((x).size()))
typedef long long ll;
typedef long double ldb;
typedef pair<int,int> pii;

const int MAXN=2e5+10;
const double eps=1e-9;
const double pi=acos(-1);

inline bool dcmp(double x,double y=0){ return fabs(x-y)<=eps; }

struct Vec  // 向量 
{
    double x,y;
    Vec(double xx=0,double yy=0){ x=xx; y=yy; }

    // 向量四则运算 
    inline Vec operator + (const Vec &p) const { return Vec(x+p.x,y+p.y); }
    inline Vec operator - (const Vec &p) const { return Vec(x-p.x,y-p.y); }
    inline Vec operator * (const double &d) const { return Vec(x*d,y*d); }
    inline Vec operator / (const double &d) const { return Vec(x/d,y/d); }
};
typedef Vec Pt;  // 把点看成从原点出发,终点为该点的向量 

inline double dot(const Vec &a,const Vec &b){ return a.x*b.x+a.y*b.y; }     // 向量点乘 
inline double cross(const Vec &a,const Vec &b){ return a.x*b.y-a.y*b.x; }   // 向量叉乘 
inline double len(const Vec &a){ return sqrt(dot(a,a)); }           // 向量模长
inline double len_sq(const Vec &a){ return dot(a,a); }              // 向量模长的平方 
inline double dis(const Pt &a,const Pt &b){ return len(b-a); }      // 两点距离 

// 将向量 a 逆时针旋转弧度 delta 
inline Vec rolate(const Vec &a,const double &rad)
{
    double t=atan2(a.y,a.x)+rad,l=len(a);
    return Vec(l*cos(t),l*sin(t));
}

struct Line // 直线
{
    Pt a,b; //用两点表示一条直线
    double ang; //直线与 x 轴的夹角(极角) 

    // 注意这里的直线是有向直线 , 规定表示方向的向量是 b-a
    // 因为我们要分出这条直线的左侧和右侧两个部分 , 所以直线必须有向  
    Line(){}
    Line(Pt aa,Pt bb){ a=aa; b=bb; ang=atan2((b-a).y,(b-a).x); }

    inline bool include(const Pt &p){ return dcmp(cross(a-p,b-p)); }    // 判断一点是否被直线包含 
};

struct Seg // 基本跟直线一模一样 , 有时用的时候方便 , 所以就写了 
{
    Pt a,b; // 两个端点

    Seg(){}
    Seg(Pt aa,Pt bb){ a=aa; b=bb; }

    inline bool include(const Pt &p){ return dcmp(cross(a-p,b-p)) && dot(a-p,b-p)<=0; } 
};

/*
* 判断两直线的位置关系
* 相交 : 1
* 平行 : 0
* 重合 : -1 
*/
inline int relation(Line &a,Line &b)    
{
    if(a.include(b.a) && a.include(b.b)) return -1;
    else if(dcmp(cross(a.b-a.a,b.b-b.a))) return 0;
    return 1;
}

// 求两直线交点,注意求之前一定要先判定两直线有无交点 
inline Pt jiao_dian(Line &a,Line &b)
{
    assert(relation(a,b)==1);
    double s1=cross(b.a-a.a,b.b-a.a),s2=cross(b.b-a.b,b.a-a.b);
    return a.a+(a.b-a.a)*s1/(s1+s2);
}

struct Poly // 多边形 
{
    vector<Pt> pts;     // 顶点按逆时针排列 

    inline bool include(const Pt &p)    // 判断点是否在多边形内 , 射线法 
    {
        int cnt=0;
        for(int i=0;i<SZ(pts);i++)
        {
            Pt a=pts[i],b=pts[(i+1)%SZ(pts)];
            if(Seg(a,b).include(p)) return true;

            double d1=a.y-p.y,d2=b.y-p.y,crs=cross(a-p,b-p);
            if((crs<=0 && d1>0 && d2<0) || (crs>=0 && d1<0 &&d2>0)) cnt++;
        }
        return cnt&1;
    }

    inline double area()    // 多边形面积 , 三角剖分法 
    {
        double res=0;
        for(int i=0;i<SZ(pts);i++)
        {
            Pt a=pts[i],b=pts[(i+1)%SZ(pts)];
            res+=cross(a,b);
        }
        return res/2;
    }
};

// Andrew 算法求凸包时需要的排序 
inline bool cmp1(const Pt &a,const Pt &b)
{
    if(a.x==b.x) return a.y>b.y;
    return a.x<b.x;
}

/* 
** Andrew 算法求凸包
** 主要思想 : 用单调栈维护斜率的单调性 , 求出凸包 
*/
Pt h[MAXN];
int cnt_dian; 
inline void tubao(Pt *p,int n)
{
    sort(p+1,p+n+1,cmp1);

    int tp=0;
    static int st[MAXN];
    static bool used[MAXN];
    st[++tp]=1;

    for(int i=2;i<=n;i++)   // 求下凸壳 
    {
        while(tp>=2 && cross(p[st[tp]]-p[st[tp-1]],p[i]-p[st[tp]])<=0)
            used[st[tp--]]=false;
        used[i]=true;
        st[++tp]=i;
    }

    int tmp=tp;
    for(int i=n-1;i>=1;i--) // 求上凸壳 
        if(!used[i])
        {
            while(tp>tmp && cross(p[st[tp]]-p[st[tp-1]],p[i]-p[st[tp]])<=0)
                used[st[tp--]]=false;
            used[i]=true;
            st[++tp]=i;
        }

    for(int i=1;i<=tp;i++)
        h[i]=p[st[i]];
    cnt_dian=tp-1;

//#define OUT_TUBAO
#ifdef OUT_TUBAO    
    double ans=0;
    for(int i=2;i<=tp;i++)
        ans+=len(h[i]-h[i-1]);
    printf("%.2lf\n",ans);
#endif
}

// 判断点 p 是否在直线 l 右侧 
inline bool onr(const Line &l,const Pt &p)
{
    Pt a=l.a,b=l.b; // 再次强调 , 直线是有向直线 , 方向向量为 b-a  
    return cross(b-p,a-p)>eps;
}

// 求半平面交对直线的极角排序 
inline bool cmp2(const Line &x,const Line &y)
{
    if(dcmp(x.ang,y.ang)) return cross(y.a-x.a,y.b-x.a)>eps;    // 如果极角相等 , 因为是取左侧半平面交 , 所以把左侧的直线排在前面 
    return x.ang<y.ang;
}

// 增量法 求半平面交 
// 求向量左侧半平面的交集 
inline void half_section(Line *line,int n)
{
    sort(line+1,line+n+1,cmp2);

    int head=1,tail=0;
    static int q[MAXN];
    static Pt dian[MAXN];

    for(int i=1;i<=n;i++)
    {
        if(dcmp(line[i].ang,line[i-1].ang)) continue;

        // 注意顺序 , 一定要先弹队尾 , 再弹队首 , 具体原因画一下三条线的图就知道了 
        while(head<tail && onr(line[i],dian[tail])) tail--;     // 最后一个交点在当前加入直线右侧 , 弹出队尾 
        while(head<tail && onr(line[i],dian[head+1])) head++;   // 第一个交点在当前加入直线右侧 , 弹出队首 

        q[++tail]=i;
        if(head<tail)
            dian[tail]=jiao_dian(line[q[tail]],line[q[tail-1]]);
    }

    while(head<tail && onr(line[q[head]],dian[tail])) tail--;    // 用队首弹出队尾 
    dian[tail+1]=jiao_dian(line[q[head]],line[q[tail]]); tail++; // 求队首与队尾的交点 

//#define OUT_AREA 
#ifdef OUT_AREA
    Poly poly;
    for(int i=head+1;i<=tail;i++)
        poly.pts.push_back(dian[i]);
    printf("%.3lf\n",poly.area());
#endif
}

// 旋转卡壳算法 求凸包直径
// 传入的 p[] 必须是已经逆时针排好序之后的凸包的顶点 
inline void get_Mx(Pt *p,int n)
{
    double Mx=0;
    int cur=2;
    p[n+1]=p[1];
    for(int i=1;i<=n;i++)
    {
        while(cross(p[i]-p[cur],p[i+1]-p[cur])<cross(p[i]-p[cur+1],p[i+1]-p[cur+1]))
        {
            cur++;
            if(cur>n) cur=1;
        }
        //Mx=max(Mx,len(p[i]-p[cur]));
        Mx=max(Mx,len_sq(p[i]-p[cur]));
    }

//#define OUT_MAX
#ifdef OUT_MAX
//  printf("%.3lf\n",Mx);
    printf("%lld\n",(ll)Mx);
#endif
}

int main()
{

    return 0;
}