计算几何(2)

请添加图片描述



下面内容大部分来自博客:ACM篇计算几何
作者:林夕林夕

一、凸包板子

  1. Graham Scan
    Graham Scan算法的一点理解:不管顺时针逆时针旋转遍历.若遍历到的元素在栈顶元素和次栈顶元素连城的直线外面则先出栈再改元素进栈;若在直线内部则直接入栈
const int maxn = 1e3 + 5;
struct Point {
    
    
    double x, y;
    Point(double x = 0, double y = 0):x(x),y(y){
    
    }
};
typedef Point Vector;
Point lst[maxn];
int stk[maxn], top;
Vector operator - (Point A, Point B){
    
    
    return Vector(A.x-B.x, A.y-B.y);
}
int sgn(double x){
    
    
    if(fabs(x) < eps)
        return 0;
    if(x < 0)
        return -1;
    return 1;
}
double Cross(Vector v0, Vector v1) {
    
    
    return v0.x*v1.y - v1.x*v0.y;
}
double Dis(Point p1, Point p2) {
    
     //计算 p1p2的 距离
    return sqrt((p2.x-p1.x)*(p2.x-p1.x)+(p2.y-p1.y)*(p2.y-p1.y));
}
bool cmp(Point p1, Point p2) {
    
     //极角排序函数 ,角度相同则距离小的在前面
    int tmp = sgn(Cross(p1 - lst[0], p2 - lst[0]));
    if(tmp > 0)
        return true;
    if(tmp == 0 && Dis(lst[0], p1) < Dis(lst[0], p2))
        return true;
    return false;
}
//点的编号0 ~ n - 1
//返回凸包结果stk[0 ~ top - 1]为凸包的编号
void Graham(int n) {
    
    
    int k = 0;
    Point p0;
    p0.x = lst[0].x;
    p0.y = lst[0].y;
    for(int i = 1; i < n; ++i) {
    
    
        if( (p0.y > lst[i].y) || ((p0.y == lst[i].y) && (p0.x > lst[i].x)) ) {
    
    
            p0.x = lst[i].x;
            p0.y = lst[i].y;
            k = i;
        }
    }
    lst[k] = lst[0];
    lst[0] = p0;
    sort(lst + 1, lst + n, cmp);
    if(n == 1) {
    
    
        top = 1;
        stk[0] = 0;
        return ;
    }
    if(n == 2) {
    
    
        top = 2;
        stk[0] = 0;
        stk[1] = 1;
        return ;
    }
    stk[0] = 0;
    stk[1] = 1;
    top = 2;
    for(int i = 2; i < n; ++i) {
    
    
        while(top > 1 && Cross(lst[stk[top - 1]] - lst[stk[top - 2]], lst[i] - lst[stk[top - 2]]) <= 0)
            --top;
        stk[top] = i;
        ++top;
    }
    return ;
}

  1. Andrew
struct Point {
    
    
    double x, y;
    Point(double x = 0, double y = 0):x(x),y(y){
    
    }
};
typedef Point Vector;
Vector operator - (Point A, Point B){
    
    
    return Vector(A.x-B.x, A.y-B.y);
}
bool operator < (const Point& a, const Point& b){
    
    
    if(a.x == b.x)
        return a.y < b.y;
    return a.x < b.x;
}
double Cross(Vector v0, Vector v1) {
    
    
    return v0.x*v1.y - v1.x*v0.y;
}
//计算凸包,输入点数组为 p,个数为 n, 输出点数组为 ch。函数返回凸包顶点数
//如果不希望凸包的边上有输入点,则把两个 <= 改为 <
//在精度要求高时建议用dcmp比较
//输入不能有重复点,函数执行完后输入点的顺序被破坏
int ConvexHull(Point* p, int n, Point* ch) {
    
    
    sort(p, p+n);
    int m = 0;
    for(int i = 0; i < n; ++i) {
    
    
        while(m > 1 && Cross(ch[m-1] - ch[m-2], p[i] - ch[m-2]) < 0) {
    
    
            m--;
        }
        ch[m++] = p[i];
    }
    int k = m;
    for(int i = n-2; i>= 0; --i) {
    
    
        while(m > k && Cross(ch[m-1] - ch[m-2], p[i] - ch[m-2]) < 0) {
    
    
            m--;
        }
        ch[m++] = p[i];
    }
    if(n > 1)
        --m;
    return m;
}

二、半平面交

增量法.

const double eps = 1e-6;
struct Point{
    
    
    double x, y;
    Point(double x = 0, double y = 0):x(x),y(y){
    
    }
};
typedef Point Vector;
Vector operator + (Vector A, Vector B){
    
    
    return Vector(A.x+B.x, A.y+B.y);
}
Vector operator - (Point A, Point B){
    
    
    return Vector(A.x-B.x, A.y-B.y);
}
Vector operator * (Vector A, double p){
    
    
    return Vector(A.x*p, A.y*p);
}
int sgn(double x){
    
    
    if(fabs(x) < eps)
        return 0;
    if(x < 0)
        return -1;
    return 1;
}
double Dot(Vector A, Vector B){
    
    
    return A.x*B.x + A.y*B.y;
}
double Cross(Vector A, Vector B){
    
    
    return A.x*B.y-A.y*B.x;
}
double Length(Vector A){
    
    
    return sqrt(Dot(A, A));
}
Vector Normal(Vector A){
    
    //向量A左转90°的单位法向量
    double L = Length(A);
    return Vector(-A.y/L, A.x/L);
}
struct Line{
    
    
    Point p;//直线上任意一点
    Vector v;//方向向量,它的左边就是对应的半平面
    double ang;//极角,即从x轴正半轴旋转到向量v所需要的角(弧度)
    Line(){
    
    }
    Line(Point p, Vector v) : p(p), v(v){
    
    
        ang = atan2(v.y, v.x);
    }
    bool operator < (const Line& L) const {
    
    //排序用的比较运算符
        return ang < L.ang;
    }
};
//点p在有向直线L的左侧
bool OnLeft(Line L, Point p){
    
    
    return Cross(L.v, p - L.p) > 0;
}
//两直线交点。假定交点唯一存在
Point GetIntersection(Line a, Line b){
    
    
    Vector u = a.p - b.p;
    double t = Cross(b.v, u)/Cross(a.v, b.v);
    return a.p + a.v*t;
}
//半平面交的主过程
int HalfplaneIntersection(Line* L, int n, Point* poly){
    
    
    sort(L, L + n);//按照极角排序
    int fst = 0, lst = 0;//双端队列的第一个元素和最后一个元素
    Point *P = new Point[n];//p[i] 为 q[i]与q[i + 1]的交点
    Line *q = new Line[n];//双端队列
    q[fst = lst = 0] = L[0];//初始化为只有一个半平面L[0]
    for(int i = 1; i < n; ++i){
    
    
        while(fst < lst && !OnLeft(L[i], P[lst - 1])) --lst;
        while(fst < lst && !OnLeft(L[i], P[fst])) ++fst;
        q[++lst] = L[i];
        if(sgn(Cross(q[lst].v, q[lst - 1].v)) == 0){
    
    
            //两向量平行且同向,取内侧一个
            --lst;
            if(OnLeft(q[lst], L[i].p)) q[lst] = L[i];
        }
        if(fst < lst)
            P[lst - 1] = GetIntersection(q[lst - 1], q[lst]);
    }
    while(fst < lst && !OnLeft(q[fst], P[lst - 1])) --lst;
    //删除无用平面
    if(lst - fst <= 1) return 0;//空集
    P[lst] = GetIntersection(q[lst], q[fst]);//计算首尾两个半平面的交点
    //从deque复制到输出中
    int m = 0;
    for(int i = fst; i <= lst; ++i) poly[m++] = P[i];
    return m;
}

三、分治法解决平面最近点对

O(nlogn)

const int maxn = 2e5 + 5;
const double inf = 1e10;

struct Point {
    
    
    double x,y;
    bool operator <(const Point &a)const {
    
    
        return x < a.x;
    }
};

inline double dist(const Point &p1, const Point &p2) {
    
    
    return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
}

Point p[maxn], q[maxn];
double ClosestPair(int l, int r) {
    
    
    if(l == r)
        return inf;
    int mid = (l+r)>>1;
    double tx = p[mid].x;
    int tot = 0;
    double ret = min(ClosestPair(l, mid), ClosestPair(mid + 1, r));
    for(int i = l, j = mid + 1; (i <= mid || j <= r); ++i) {
    
    
        while(j <= r && (p[i].y > p[j].y || i > mid)) {
    
    
            q[tot++] = p[j];
            j++; //归并按y排序
        }
        if(abs(p[i].x - tx) < ret && i <= mid) {
    
     //选择中间符合要求的点
            for(int k = j - 1; k > mid && j - k < 3; --k)
                ret = min(ret, dist(p[i], p[k]));
            for(int k = j; k <= r && k-j < 2; ++k)
                ret = min(ret, dist(p[i], p[k]));
        }
        if(i <= mid)
            q[tot++] = p[i];
    }
    for(int i = l, j = 0; i <= r; ++i, ++j)
        p[i] = q[j];
    return ret;
}

四、旋转卡壳(O(nlogn)解决平面最远点对)

double Dist2(Point p1, Point p2) {
    
     //计算距离的平方
    double ret = Dot(p1 - p2, p1 - p2);
    return ret;
}
double RotatingCalipers(Point* ch, int m) {
    
    //返回平面最大距离的平方
    if(m == 1) return 0.0;
    if(m == 2) return Dist2(ch[0], ch[1]);
    double ret = 0.0;
    ch[m] = ch[0];
    int j = 2;
    for(int i = 0; i < m; ++i) {
    
    
        while(Cross(ch[i + 1] - ch[i], ch[j] - ch[i]) < Cross(ch[i + 1] - ch[i], ch[j + 1] - ch[i]))
            j = (j + 1)%m;
        ret = max(ret, max(Dist2(ch[j], ch[i]), Dist2(ch[j], ch[i + 1])));
    }
    return ret;
}

五、三点确定外接圆圆心坐标

struct Point {
    
    
    double x,y;
    Point(double x = 0, double y = 0):x(x),y(y){
    
    }
};
Point Excenter(Point a, Point b, Point c){
    
    
    double a1 = b.x - a.x;
    double b1 = b.y - a.y;
    double c1 = (a1*a1 + b1*b1)/2;
    double a2 = c.x - a.x;
    double b2 = c.y - a.y;
    double c2 = (a2*a2 + b2*b2)/2;
    double d = a1*b2 - a2*b1;
    return Point(a.x + (c1*b2 - c2*b1)/d, a.y + (a1*c2 - a2*c1)/d);
}

猜你喜欢

转载自blog.csdn.net/m0_50435987/article/details/121257532