Skip to content

计算几何

点和线

精度控制

const double eps = 1e-10;
double dcmp(double x) { //判断 正/负/0
    if(fabs(x) < eps) return 0;
    else return x > 0 ? 1 : -1;
}

点和向量存储

struct Vector{
    double x, y;
    Point(double x = 0, y = 0):x(x), y(y){}
    Vector rotate_90() {return Vector(y, -x);} //顺时针90度
    Vector rotate_90_c() {return Vector(-y, x);} //逆时针90度
};
typedef Vector Point;

向量的四则运算

//这种返回方式似乎有时候会出问题
//向量+向量=向量  点+向量=点
Vector operator+(Vector a, Vector b){ 
    return Vector(a.x+b.x, a.y+b.y); 
}
//点-点=向量
Vector operator-(Vector a, Vector 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); 
}
//向量/数=向量
Vector operator/(Vector a, double p){ 
    return Vector(a.x/p. a.y/p); 
}

点积 叉积 及 应用

\(\vec{a} \cdot \vec{b} = |a||b|cos\theta\)

\(|\vec{a} \times \vec{b}| = |a||b|sin\theta\),正负由两向量的相对关系决定

//点积
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)); 
}
//求角度
double Angle(Vector a, Vector b){ 
    return acos(Dot(a, b)/Length(a)/Length(b));
}

判断点是否在线段上

bool OnSegment(Point p1, Point p2, Point q){  //p1-p2是线段
    return Cross(p1-q, p2-q) == 0 && Dot(p1-q, p2-q) <= 0;
}

求两直线的交点

\(q_2,q_1,p_2,p_1\)​​确定的两条直线,假设交点为 \(p_1+t(p_2-p_1)\)​​​

\((q_2-q_1) \times (p_1 + t(p_2-p_1)-q_1)=0\)

\(t = \frac{(q_2-q_1)\times(q_1-p_1)}{(q_2-q_1)\times(p_2-p_1)}\)

/*Point GetLineIntersection(Point p1, Point p2, Point q1, Point q2){
    return p1+(p2-p1)*Cross(q2-q1, q1-p1)/Cross(q2-q1, p2-p1)
}*/
Point GetLineIntersection(Point p, Vector v1, Point q, Vector v2){
    return p+v1*Cross(v2, q-p)/Cross(v2, v1);
} //平行特判

线段相交判定

跨立实验:判断q1,q2在线段p2-p1两侧 且 p1,p2在线段q2-q1两侧

bool SegmentProperIntersection(Point p1, Point p2, Point q1, Point q2){
    double a1 = Cross(p2-p1, q2-p1), a2 = Cross(p2-p1, q1-p1);
    double a3 = Cross(q2-q1, p1-q1), a4 = Cross(q2-q1, p2-q1);
    return dcmp(a1)*dcmp(a2)<0 && dcmp(a3)*dcmp(a4)<0;
} 
/*特判端点在另一条线段的情况
if(OnSegment(p[i],q[i],p[j]) 
|| OnSegment(p[i],q[i],q[j]) 
|| OnSegment(p[j],q[j],p[i])
|| OnSegment(p[j],q[j],q[i])
)  G[i][j] = G[j][i] = true;
*/

线段和直线相交判定

bool fun(Point a, Point b, Point c, Point d) { //a b 确定直线     c d 确定线段
    return Cross(a - b, a - c) * Cross(a - b, a - d) <= eps
}

向量旋转与单位化

逆时针:

\[ \left[ \matrix{ x^{'} \\ y^{'} } \right] = \left[ \matrix{ x \\ y } \right] \left[ \matrix{ cos\theta & sin\theta\\ -sin\theta & cos\theta } \right] \]

顺时针:

\[ \left[ \matrix{ x^{'} \\ y^{'} } \right] = \left[ \matrix{ x \\ y } \right] \left[ \matrix{ cos\theta & -sin\theta\\ sin\theta & cos\theta } \right] \]
//rad 是弧度, 逆时针
Vector Rotate(Vector a, double rad) {
    return Vector(a.x*cos(rad)-a.y*sin(rad), a.x*sin(rad)+a.y*soc(rad));
}
//单位化
Vector Normal(Vector a) {
    double L = length(a);
    return Vector(a.x/L, a.y/L);
}

点到直线的距离

double DistaceToLine(Point p, Point a, Point b) {
    Vector v1 = b-a, v2 = p-a;
    return fabs(Cross(v1, v2))/Length(v1); //平行四边形面积 除/底=高
}

圆的存储

struct Circle{
    Point c;
    double r;
    Circle(Point a, double b = 0):c(a), r(b){}
}

三角形外接圆

//三点不共线
Circle circumcircle(Point p1, Point p2, Point p3) {
    Point p = GetLineIntersection((p1+p2)/2, (p2-p1).rotate_90(), (p1+p3)/2, (p3-p1).rotate_90());
    return Circle(p, Length(p1-p));
}

最小圆覆盖

随机增量法 \(O(n)\)

//洛谷 P1742
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;

const int maxn = 1e5+5;
typedef struct Vector{
    double x, y;
    Vector(double a = 0, double b = 0):x(a),y(b){}
    Vector rotate_90(){return Vector(y, -x); }
}Point;

struct Circle{
    Point c;
    double r;
    Circle(Point a, double b):c(a),r(b){}
};

Point operator-(const Point& a, const Point& b){ return Vector(a.x-b.x, a.y-b.y); }
Point operator+(const Point& a, const Point& b){ return Vector(a.x+b.x, a.y+b.y); }
Point operator/(const Point& a, const double& b){ return Vector(a.x/b, a.y/b); }
Point operator*(const Point& a, const double& b){ return Vector(a.x*b, a.y*b); }
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)); }

Point GetLineIntersection(Point p, Vector v1, Point q, Vector v2){
    return p+v1*Cross(v2, q-p)/Cross(v2, v1);
}

Circle circumcircle(Point p1, Point p2, Point p3){
    Point p = GetLineIntersection((p1+p2)/2, (p2-p1).rotate_90(), (p1+p3)/2, (p3-p1).rotate_90());
    return Circle(p, Length(p1-p));
}

Circle min_circle(Point *p, int n)  { //最小圆覆盖
    random_shuffle(p+1, p+1+n);
    Point o;
    double r = 0;
    for(int i = 1; i <= n; i++)
        if(Length(p[i]-o) > r)
        {
            o = p[i], r = 0;
            for(int j = 1; j < i; j++)
                if(Length(p[j]-o) > r)
                {
                    o = (p[i]+p[j])/2, r = Length(p[i]-p[j])/2;
                    for(int k = 1; k < j; k++)
                        if(Length(o-p[k]) > r)
                        {
                            Circle tmp = circumcircle(p[i], p[j], p[k]);
                            o = tmp.c, r = tmp.r;
                        }
                }
        }
    return Circle(o, r);
} 

int main(){
    int n;
    Point p[maxn];
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> p[i].x >> p[i].y;
    Circle res =  min_circle(p, n);
    printf("%.10lf\n%.10lf %.10lf\n", res.r, res.c.x, res.c.y);
    return 0;
}

凸包

Graham扫描法 算凸包

按x,y坐标升序排列,确定凸包的2的顶点,然后再确定上下两个凸壳

排序 \(O(nlogn)\)​​ 其他 \(O(n)\)​​

bool cmp(Point &p, Point &q){
    if(p.x != p.x) return p.x < q.x;
    else return p.y < q.y;
}

vector<Point> ConvexHull(Point *ps, int n){
    sort(ps, ps+n, cmp);
    int k = 0;  //凸包顶点数
    vector<Point> qs(n*2);
    //下凸壳
    for(int i = 0; i < n; i++){  //while要注意
        while(k > 1 && Cross(qs[k-1]-qs[k-2], ps[i]-qs[k-1] <= 0)) k--;
        qs[k++] = ps[i];
    }
    //上凸壳
    for(int i = n-2, t = k; i >= 0; i--){
        while(k > t && Cross(qs[k-1]-qs[k-2], ps[i]-qs[k-1] <= 0)) k--;
        qs[k++] = ps[i];
    }
    qs.resize(k-1);
    return qs;
}