计算几何基础

文章的绝大部分内容参(chao)考(xi)自Menci菊苣的blog,放在这主要是为了自己参考,做了一点点自己比较适应的改动(其实是C++水平太低看不懂233)
原blog链接:https://oi.men.ci/geometry-notes/

这里写图片描述

const double EPS=1e-10;
bool dcmp(double x,double y=0)
{
    return fabs(x-y)<EPS;
}

这里写图片描述
这里写图片描述
定义一个结构体Vec用于存储向量,同时在内部重载运算符方便使用;Pt则是点的名称;

typedef struct Vec{
    double x,y;
    Vec(double x=0,double y=0):x(x),y(y){}
    Vec operator+(const Vec v)const{
        return Vec(x+v.x,y+v.y);
    }
    Vec operator-(const Vec v)const{
        return Vec(x-v.x,y-v.y);
    }
    Vec operator*(double d)const{
        return Vec(x*d,y*d);
    }
    Vec operator/(double d)const{
        return Vec(x/d,y/d);
    }
    double form()const{
        return x*x+y*y;
    }
}Pt;

这里写图片描述

double dot(Vec a,Vec b)
{
    return a.x*b.x+a.y*b.y;
}//点乘
double cross(Vec a,Vec b)
{
    return a.x*b.y-a.y*b.x;
}//叉乘

直线:(用Line结构体存储,里面包含两个Pt类型的数据,用于表示直线上的两个点):
这里写图片描述

struct Line
{
    Pt a, b;

    Line() {} // 提供一个不需要参数的构造函数
    Line(const Pt &a, const Pt &b) : a(a), b(b) {}

    bool include(const Pt &p) const//判断点是否在直线上
    {
        return dcmp(cross(a - p, b - p));
    }

    // 两直线关系(交点个数)
    // 0 表示平行(无交点)
    // 1 表示相交(一个交点)
    // -1 表示重合(无数个交点)
    int relation(const Line &a, const 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;
        else return 1;
    }

    // 求两直线交点(需要保证两直线有交点)
    Pt intersect(const Line &a, const Line &b)
    {
        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);
    }
};

Line中的relation函数用于判断两直线的关系:
这里写图片描述
intersact函数用于计算两直线的交点:
这里写图片描述

线段则用segment结构体存储,同样是包含两个Pt数据类型,但是在比较函数方面有一些不同:

// 线段(Segment),用两个点表示
struct Seg
{
    Pt a, b;

    Seg(const Pt &a, const Pt &b) : a(a), b(b) {}

    // 线段包含点(点在线段上)
    bool include(const Pt &p)
    {
        // PA × PB = 0:PA 与 PB 共线,即点在线段所在的直线上
        // PA · PB = 0:PA 与 PB 方向不同(A 和 B 分别在 P 的两边),如果 PA · PB = 0 则 P = A 或 P = B
        return dcmp(cross(a - p, b - p)) && dot(a - p, b - p) <= 0;
    }
};

这里写图片描述
这里写图片描述
(不过,线段所在的直线重合时需要特判,如果直线重合,需要保证两线段中至少有一个线段的一个端点包含在另一线段中;不重合时则求出两直线的交点,并且该交点必须同时在两条线段上才行);

多边形
这里写图片描述
这里写图片描述

struct Poly
{
    std::vector<Pt> pts;

    bool include(const Pt &p) const//判断点是否在多边形内
    {
        int cnt = 0;
        // 判断与每条边有没有交点
        for (size_t i = 0; i < pts.size(); i++)
        {
            // 枚举相邻的每两个点
            const Pt &a = pts[i], &b = pts[(i + 1) % pts.size()];

            // 如果点 P 在边 AB 上
            if (Seg(a, b).include(p)) return true;

            // 详见图
            double d1 = a.y - p.y, d2 = b.y - p.y, tmp = cross(a - p, b - p);
            if ((tmp >= 0 && d1 >= 0 && d2 < 0) || (tmp <= 0 && d1 < 0 && d2 >= 0)) cnt++;
        }

        // 奇数的交点
        return cnt % 2 == 1;
    }
};

多边形面积:
这里写图片描述

// 多边形面积(有向面积)
double area() const
{
    double res = 0;
    for (size_t i = 0; i < pts.size(); i++)
    {
        // 枚举每两个点
        const Pt &a = pts[i], &b = pts[(i + 1) % pts.size()];
        res += cross(a, b);
    }
    return res / 2;
}

凸包:
这里写图片描述
注意:算凸包时将点记录在外部的Pt点集中,然后把计算出来的结果保存在Poly结构体中的vector中,再调用结构体中的函数计算凸包面积;与普通的多边形直接将点记录在结构体中的vector区分开!!

// 求凸包用的点
int n;
Pt a[MAXN + 1];

// 凸包极角排序的比较函数
inline bool compare(const Pt &a, const Pt &b)
{
    // 两个向量
    Vec va = a - ::a[1], vb = b - ::a[1];
    double t = cross(va, vb);
    if (!dcmp(t)) return t > 0; // OA -> OB 是逆时针,则 A 极角序在先
    else return va.norm() < vb.norm(); // norm 较小的长度较小
}

struct Poly
{
    std::vector<Pt> pts;

    // 求凸包(Convex),结果储存在自身 pts 中
    void convex()
    {
        // 找出最左下角的点
        int id = 1;
        for (int i = 1; i <= n; i++)
        {
            if (a[i].x < a[id].x || (a[i].x == a[id].x && a[i].y < a[id].y)) id = i;
        }
        if (id != 1) std::swap(a[1], a[id]);

        // 排序
        std::sort(a + 2, a + n + 1, &compare);

        // 极角序扫描
        pts.push_back(a[1]);
        for (int i = 2; i <= n; i++)
        {
            // 比较,如果最后一个点需要被删掉则弹出(pop_back)
            while (pts.size() >= 2 && cross(pts.back() - pts[pts.size() - 2], a[i] - pts.back()) <= 0) pts.pop_back();
            pts.push_back(a[i]);
        }
    }
};

完整代码:

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>

const int MAXN = 100000;
const double EPS = 1e-8;

// 带误差比较
inline bool dcmp(double x, double y = 0)
{
    return fabs(x - y) <= EPS;
}

/* 
 * 向量(Vector)或点
 *
 * 使用原点到一个点 (x, y) 的有向线段表示向量
 * 从点 A 到点 B 的向量表示为 A - B
 */
typedef struct Vec
{
    double x, y;

    Vec(double x = 0, double y = 0) : x(x), y(y) {}

    // 相加
    Vec operator+(const Vec &v) const
    {
        return Vec(x + v.x, y + v.y);
    }

    // 相减
    Vec operator-(const Vec &v) const
    {
        return Vec(x - v.x, y - v.y);
    }

    // 数乘(伸长、缩短)
    Vec operator*(double d) const
    {
        return Vec(x * d, y * d);
    }

    Vec operator/(const double d) const
    {
        return Vec(x / d, y / d);
    }

    // 范数,用来比较长度,等于长度的平方
    double norm() const
    {
        return x * x + y * y;
    }
} Pt;

// 点乘
double dot(const Vec &a, const Vec &b)
{
    return a.x * b.x + a.y * b.y;
}

// 叉乘
double cross(const Vec &a, const Vec &b)
{
    return a.x * b.y - a.y * b.x;
}

// 线段(Segment),用两个点表示
struct Seg
{
    Pt a, b;

    Seg(const Pt &a, const Pt &b) : a(a), b(b) {}

    // 线段包含点(点在线段上)
    bool include(const Pt &p)
    {
        // PA × PB = 0:PA 与 PB 共线,即点在线段所在的直线上
        // PA · PB = 0:PA 与 PB 方向不同(A 和 B 分别在 P 的两边),如果 PA · PB = 0 则 P = A 或 P = B
        return dcmp(cross(a - p, b - p)) && dot(a - p, b - p) <= 0;
    }
};

// 直线,用两个点表示
struct Line
{
    Pt a, b;

    Line() {} // 提供一个不需要参数的构造函数
    Line(const Pt &a, const Pt &b) : a(a), b(b) {}

    bool include(const Pt &p) const
    {
        return dcmp(cross(a - p, b - p));
    }

    // 两直线关系(交点个数)
    // 0 表示平行(无交点)
    // 1 表示相交(一个交点)
    // -1 表示重合(无数个交点)
    static int relation(const Line &a, const 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;
        else return 1;
    }

    // 求两直线交点(需要保证两直线有交点)
    static Pt intersect(const Line &a, const Line &b)
    {
        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);
    }
};

// 求凸包用的点
int n;
Pt a[MAXN + 1];

// 凸包极角排序的比较函数
inline bool compare(const Pt &a, const Pt &b)
{
    // 两个向量
    Vec va = a - ::a[1], vb = b - ::a[1];
    double t = cross(va, vb);
    if (!dcmp(t)) return t > 0; // OA -> OB 是逆时针,则 A 极角序在先
    else return va.norm() < vb.norm(); // norm 较小的长度较小
}

struct Poly
{
    std::vector<Pt> pts;

    bool include(const Pt &p) const
    {
        int cnt = 0;
        // 判断与每条边有没有交点
        for (size_t i = 0; i < pts.size(); i++)
        {
            // 枚举相邻的每两个点
            const Pt &a = pts[i], &b = pts[(i + 1) % pts.size()];

            // 如果点 P 在边 AB 上
            if (Seg(a, b).include(p)) return true;

            // 详见图
            double d1 = a.y - p.y, d2 = b.y - p.y, tmp = cross(a - p, b - p);
            if ((tmp >= 0 && d1 >= 0 && d2 < 0) || (tmp <= 0 && d1 < 0 && d2 >= 0)) cnt++;
        }

        // 奇数的交点
        return cnt % 2 == 1;
    }

    // 多边形面积(有向面积)
    double area() const
    {
        double res = 0;
        for (size_t i = 0; i < pts.size(); i++)
        {
            // 枚举每两个点
            const Pt &a = pts[i], &b = pts[(i + 1) % pts.size()];
            res += cross(a, b);
        }
        return res / 2;
    }

    // 求凸包(Convex),结果储存在自身 pts 中
    void convex()
    {
        // 找出最左下角的点
        int id = 1;
        for (int i = 1; i <= n; i++)
        {
            if (a[i].x < a[id].x || (a[i].x == a[id].x && a[i].y < a[id].y)) id = i;
        }
        if (id != 1) std::swap(a[1], a[id]);

        // 排序
        std::sort(a + 2, a + n + 1, &compare);

        // 极角序扫描
        pts.push_back(a[1]);
        for (int i = 2; i <= n; i++)
        {
            // 比较,如果最后一个点需要被删掉则弹出(pop_back)
            while (pts.size() >= 2 && cross(a[i] - pts[pts.size() - 2], pts.back() - pts[pts.size() - 2]) >= 0) pts.pop_back();
            pts.push_back(a[i]);
        }
    }
};

POJ1127:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int par[20];
int find(int x)
{
    return x==par[x]?x:par[x]=find(par[x]);
}
bool unite(int x,int y)
{
    x=find(x),y=find(y);
    if(x==y)return false;
    par[x]=y;return true;
}
const double EPS=1e-10;
bool dcmp(double x,double y=0)
{
    return fabs(x-y)<EPS;
}
typedef struct Vec{
    double x,y;
    Vec(double x=0,double y=0):x(x),y(y){}
    Vec operator+(const Vec v)const{
        return Vec(x+v.x,y+v.y);
    }
    Vec operator-(const Vec v)const{
        return Vec(x-v.x,y-v.y);
    }
    Vec operator*(double d)const{
        return Vec(x*d,y*d);
    }
    Vec operator/(double d)const{
        return Vec(x/d,y/d);
    }
    double form()const{
        return x*x+y*y;
    }
}Pt;
double dot(Vec a,Vec b)
{
    return a.x*b.x+a.y*b.y;
}//点乘
double cross(Vec a,Vec b)
{
    return a.x*b.y-a.y*b.x;
}
struct Line{
    Pt a,b;
    bool include(const Pt p)const{//点是否在直线上
        return dcmp(cross(a-p,b-p));
    }
    bool include1(const Pt p) const//点是否在线段上
    {
        // PA × PB = 0:PA 与 PB 共线,即点在线段所在的直线上
        // PA · PB = 0:PA 与 PB 方向不同(A 和 B 分别在 P 的两边),如果 PA · PB = 0 则 P = A 或 P = B
        return dcmp(cross(a - p, b - p)) && dot(a - p, b - p) <= 0;
    }
};
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;//平行
    else return 1;//相交
}
// 求两直线交点(需要保证两直线有交点)
Pt intersect(const Line &a, const Line &b)
{
    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);
}
bool check(Line a,Line b)
{
    int sit=relation(a,b);
    if(sit==-1){//如果直线重合,需要保证两线段中至少有一个线段的一个端点包含在另一线段中
        if(a.include1(b.a)||a.include1(b.b)||b.include1(a.a)||b.include1(a.b))
            return true;
        else return false;
    }
    else if(!sit)return false;//如果直线平行,直接不可能
    else{
        Pt p=intersect(a,b);
        if(a.include1(p)&&b.include1(p))return true;//两直线交点必须包含在两个线段中
        else return false;
    }
}
int main()
{
    int n,i,j,k;Vec a,b;
    while(cin>>n&&n){
        Line l[20];
        for(i=1;i<=n;i++){
            cin>>a.x>>a.y>>b.x>>b.y;
            l[i].a=a;l[i].b=b;
            par[i]=i;
        }
        for(i=1;i<n;i++)
            for(j=i+1;j<=n;j++){
            if(check(l[i],l[j]))
                unite(i,j);
        }
        while(cin>>i>>j&&(i+j)){
            if(find(i)==find(j))
                cout<<"CONNECTED"<<endl;
            else cout<<"NOT CONNECTED"<<endl;
        }
    }

    return 0;
}

POJ3348:
凸包模板题:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#include<cmath>
using namespace std;
const int MAXN = 10005;
const double EPS = 1e-8;
// 带误差比较
inline bool dcmp(double x, double y = 0)
{
    return fabs(x - y) <= EPS;
}

typedef struct Vec
{
    double x, y;

    Vec(double x = 0, double y = 0) : x(x), y(y) {}

    // 相加
    Vec operator+(const Vec &v) const
    {
        return Vec(x + v.x, y + v.y);
    }

    // 相减
    Vec operator-(const Vec &v) const
    {
        return Vec(x - v.x, y - v.y);
    }

    // 数乘(伸长、缩短)
    Vec operator*(double d) const
    {
        return Vec(x * d, y * d);
    }

    Vec operator/(const double d) const
    {
        return Vec(x / d, y / d);
    }

    // 范数,用来比较长度,等于长度的平方
    double norm() const
    {
        return x * x + y * y;
    }
} Pt;

// 点乘
double dot(const Vec &a, const Vec &b)
{
    return a.x * b.x + a.y * b.y;
}

// 叉乘
double cross(const Vec &a, const Vec &b)
{
    return a.x * b.y - a.y * b.x;
}
struct Seg
{
    Pt a, b;

    Seg(const Pt &a, const Pt &b) : a(a), b(b) {}

    // 线段包含点(点在线段上)
    bool include(const Pt &p)
    {
        // PA × PB = 0:PA 与 PB 共线,即点在线段所在的直线上
        // PA · PB = 0:PA 与 PB 方向不同(A 和 B 分别在 P 的两边),如果 PA · PB = 0 则 P = A 或 P = B
        return dcmp(cross(a - p, b - p)) && dot(a - p, b - p) <= 0;
    }
};
int n;
Pt a[MAXN + 1];

// 凸包极角排序的比较函数
inline bool compare(const Pt &a, const Pt &b)
{
    // 两个向量
    Vec va = a - ::a[1], vb = b - ::a[1];
    double t = cross(va, vb);
    if (!dcmp(t)) return t > 0; // OA -> OB 是逆时针,则 A 极角序在先
    else return va.norm() < vb.norm(); // norm 较小的长度较小
}
struct Poly
{
    std::vector<Pt> pts;

    bool include(const Pt &p) const
    {
        int cnt = 0;
        // 判断与每条边有没有交点
        for (size_t i = 0; i < pts.size(); i++)
        {
            // 枚举相邻的每两个点
            const Pt &a = pts[i], &b = pts[(i + 1) % pts.size()];

            // 如果点 P 在边 AB 上
            if (Seg(a, b).include(p)) return true;

            // 详见图
            double d1 = a.y - p.y, d2 = b.y - p.y, tmp = cross(a - p, b - p);
            if ((tmp >= 0 && d1 >= 0 && d2 < 0) || (tmp <= 0 && d1 < 0 && d2 >= 0)) cnt++;
        }

        // 奇数的交点
        return cnt % 2 == 1;
    }

    // 多边形面积(有向面积)
    double area() const
    {
        double res = 0;
        for (size_t i = 0; i < pts.size(); i++)
        {
            // 枚举每两个点
            const Pt &a = pts[i], &b = pts[(i + 1) % pts.size()];
            res += cross(a, b);
        }
        return res / 2;
    }

    // 求凸包(Convex),结果储存在自身 pts 中
    void convex()
    {
        // 找出最左下角的点
        int id = 1;
        for (int i = 1; i <= n; i++)
        {
            if (a[i].x < a[id].x || (a[i].x == a[id].x && a[i].y < a[id].y)) id = i;
        }
        if (id != 1) std::swap(a[1], a[id]);

        // 排序
        std::sort(a + 2, a + n + 1, &compare);

        // 极角序扫描
        pts.push_back(a[1]);
        for (int i = 2; i <= n; i++)
        {
            // 比较,如果最后一个点需要被删掉则弹出(pop_back)
            while (pts.size() >= 2 && cross(a[i] - pts[pts.size() - 2], pts.back() - pts[pts.size() - 2]) >= 0) pts.pop_back();
            pts.push_back(a[i]);
        }
    }
};
int main()
{
    int i,j;
    cin>>n;
    for(i=1;i<=n;i++){
        scanf("%lf%lf",&a[i].x,&a[i].y);
    }
    Poly p;p.convex();
    cout<<(long long)p.area()/50<<endl;

    return 0;
}

猜你喜欢

转载自blog.csdn.net/humveea6/article/details/80201824