Martian148's blog Martian148's blog
首页
  • ICPC 算法笔记
  • ICPC 算法题解
  • 体系结构
  • 高等数学
  • 线性代数
  • 概率论与数理统计
  • 具体数学
  • Martian148的奇思妙想
  • 游记
  • 通识课笔记
关于
  • useful 网站
  • 友情链接
  • 分类
  • 归档

Martian148

一只热爱文科的理科生
首页
  • ICPC 算法笔记
  • ICPC 算法题解
  • 体系结构
  • 高等数学
  • 线性代数
  • 概率论与数理统计
  • 具体数学
  • Martian148的奇思妙想
  • 游记
  • 通识课笔记
关于
  • useful 网站
  • 友情链接
  • 分类
  • 归档
  • 线上赛板子(实时更新)
  • 数据结构

  • 数学

  • 计算几何

    • 二维计算几何基础(总结)
      • 定义向量
      • 基本运算
        • 点积
        • 长度
        • 角度
        • 叉积
        • 三角形面积
        • 向量旋转
        • 求单位法向量
      • 点和直线
        • 直线的定义
        • 直线交点
        • 点到直线的距离
        • 点到线段的距离
        • 点在直线上的投影
        • 线段相交判定
      • 多边形
        • 多边形面积
        • 点在多边形内判定
    • 三维计算几何基础
    • 圆相关计算
    • 凸包
    • 半平面交
  • 动态规划

  • 图论

  • 字符串

  • 杂项

  • 算法笔记
  • 计算几何
martian148
2024-08-27
目录

二维计算几何基础(总结)

# 二维计算几何基础

# 定义向量

const double eps = 1e-10;

struct Point {
    double x, y;
    Point() {}
    Point(double x, double y) : 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); }
Vector operator / (Vector A, double p) { return Vector(A.x / p, A.y / p); }

int dcmp(double x, double y = 0.0) { // 比较两个浮点数的大小
    if (fabs(x - y) < eps) return 0;
    return x < y ? -1 : 1;
}

bool operator == (const Point &a, const Point &b) {
    return dcmp(a.x, b.x) == 0 && dcmp(a.y, b.y) == 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 基本运算

# 点积

OA→⋅OB→=xAxB+yAyB\overrightarrow{OA} \cdot \overrightarrow{OB}=x_Ax_B+y_Ay_BOA⋅OB=xA​xB​+yA​yB​

double dot(Vector A, Vector B) { return A.x * B.x + A.y * B.y; }
1

# 长度

double length(Vector A) { return sqrt(dot(A, A)); }
1

# 角度

两个向量中的角度可以使用点积计算

cos<A,B>=A⋅B∣A∣∣B∣cos<A,B>=\frac{A\cdot B}{|A||B|}cos<A,B>=∣A∣∣B∣A⋅B​

double angle(Vector A, Vector B) { return acos(dot(A, B) / length(A) / length(B)); }
1

# 叉积

OA→×OB→=xAyB−xByA\overrightarrow{OA} \times \overrightarrow{OB}=x_Ay_B-x_By_AOA×OB=xA​yB​−xB​yA​

可以使用右手螺旋法则判断正负

double cross(Vector A, Vector B) { return A.x * B.y - A.y * B.x; }
1

# 三角形面积

可以利用叉积计算

double area2(Point A, Point B, Point C) { return cross(B - A, C - A); }
1

# 向量旋转

x′=xcos⁡a−ysin⁡a,y′=xsin⁡a+ycos⁡ax'=x \cos a - y \sin a,y'=x \sin a+y \cos ax′=xcosa−ysina,y′=xsina+ycosa

[x′y′]=[cos⁡x−sinxsin⁡xcosx][xy] \begin{bmatrix}x'\\y'\end{bmatrix}= \begin{bmatrix}\cos x &\ -sinx\\\sin x\ &\ cos x\end{bmatrix} \begin{bmatrix}x\\y\end{bmatrix} [x′y′​]=[cosxsinx ​ −sinx cosx​][xy​]
Vector rotate(Vector A, double rad) { // 逆时针旋转 rad 弧度
    return Vector(A.x * cos(rad) - A.y * sin(rad), A.x * sin(rad) + A.y * cos(rad));
}
1
2
3

# 求单位法向量

Vector normal (Vector A) { // 左转 90 度, 单位法向量
    double L = length(A);
    return Vector(-A.y / L, A.x / L);
}
1
2
3
4

# 点和直线

直线可以用直线上一点 P0P_0P0​ 和方向向量 vvv 表示 (虽然这个向量的大小没什么用处)。

直线上所有点 PPP 满足 P=P0+tvP=P_0+tvP=P0​+tv 其中 ttt 称为参数。如果已知直线上的两个不同点 A,BA,BA,B 则方向向量为 B−AB-AB−A ,所以参数方程为 A+(B−A)tA+(B-A)tA+(B−A)t

参数方程方便的地方在于直线,射线和线段的方程形式是一样的,区别仅仅在于参数

直线的 ttt 没有范围限制,射线的 t>0t>0t>0, 线段的 ttt 在 0∼10 \sim 10∼1 之间

# 直线的定义

struct Line {
    Point P;
    Vector v;
    Line() {}
    Line(Point P, Vector v) : P(P), v(v) {}
    Point point(double t) { return P + v * t; }
}
1
2
3
4
5
6
7

# 直线交点

设直线分别为 P+tvP+tvP+tv 和 Q+twQ+twQ+tw ,设向量 u→=QP\overrightarrow{u}=QPu=QP ,交点在第一条直线的参数为 t1t_1t1​,第二条直线上的参数为 t2t_2t2​ 则 xxx 和 yyy 坐标可以列出一个方程,解得

t1=cross(w,u)cross(v,w),t2=cross(v,u)cross(v,w) t_1=\frac{cross(w,u)}{cross(v,w)},t_2=\frac{cross(v,u)}{cross(v,w)} t1​=cross(v,w)cross(w,u)​,t2​=cross(v,w)cross(v,u)​

代码如下,调用前请确保两条直线有唯一交点,当且仅当 Cross(v,w)≠0Cross(v,w) \ne 0Cross(v,w)​=0

Point line_intersection(Point P, Vector v, Point Q, Vector w) {
    Vector u = P - Q;
    double t = cross(w, u) / cross(v, w);
    return P + v * t;
}
1
2
3
4
5
Point line_intersection(Line a, Line b) {
    Vector u = a.P - b.P;
    double t = cross(b.v, u) / cross(a.v, b.v);
    return a.point(t);
}
1
2
3
4
5

# 点到直线的距离

用叉积算出,用平行四边形的面积除以底

double distance_to_line(Point P, Point A, Point B) {
    Vector v1 = B - A, v2 = P - A;
    return fabs(cross(v1, v2) / length(v1));
}
1
2
3
4
double distance_to_line(Point P, Line l) {
    Vector v1 = l.v, v2 = P - l.P;
    return fabs(cross(v1, v2) / length(v1));
}
1
2
3
4

# 点到线段的距离

点到线段的距离有两种可能

image.png

如果投影点为 QQQ 在线段 ABABAB 上,则所求距离就是 PPP 点直线 ABABAB 的距离(左图),如果在射线 BABABA 上,则所求距离为 QAQAQA ,如果在射线 ABABAB 上,则所求距离为 QBQBQB

判断 QQQ 的位置建议用点积确定

double distance_to_segment(Point P, Point A, Point B) {
    if (A == B) return length(P - A);
    Vector v1 = B - A, v2 = P - A, v3 = P - B;
    if (dcmp(dot(v1, v2)) < 0) return length(v2);
    if (dcmp(dot(v1, v3)) > 0) return length(v3);
    return fabs(cross(v1, v2)) / length(v1);
}
1
2
3
4
5
6
7

# 点在直线上的投影

我们把直线 ABABAB 写成参数式 A+tvA+tvA+tv (vvv 为向量 ABABAB)且设 QQQ 的参数为 t0t_0t0​ 那么 Q=A+t0vQ=A+t_0vQ=A+t0​v 根据 PQPQPQ 垂直于 ABABAB,两个向量的点积应该为 000,可以得出 v⋅(P−(A+t0v))=0v \cdot (P-(A+t_0v))=0v⋅(P−(A+t0​v))=0 根据分配律有 v⋅(P−A)−t0⋅(v⋅v)=0v \cdot (P-A)-t_0 \cdot (v \cdot v)=0v⋅(P−A)−t0​⋅(v⋅v)=0 就能解出 t0t_0t0​ 了

Point line_projection(Point P, Point A, Point B) {
    Vector v = B - A;
    return A + v * (dot(v, P - A) / dot(v, v));
}
1
2
3
4

# 线段相交判定

我们定义两条线段相交的充要条件是,每条线段的两个端点都在另一条线段的两侧(叉积的符号不同),不包括在端点处相交(这种方法不能判断在端点处相交)

bool is_segment_proper_intersection(Point a1, Point a2, Point b1, Point b2) {
    double c1 = cross(a2 - a1, b1 - a1), c2 = cross(a2 - a1, b2 - a1),
           c3 = cross(b2 - b1, a1 - b1), c4 = cross(b2 - b1, a2 - b1);
    return dcmp(c1) * dcmp(c2) < 0 && dcmp(c3) * dcmp(c4) < 0;
}
1
2
3
4
5

如果允许在端点处相交

  • 如果 c1,c2c_1,c_2c1​,c2​ 都是 000 , 表示两线段共线
  • 如果 c1,c2c_1,c_2c1​,c2​ 不都是 000 ,则只有一种相交方法,即某个端点在另外一条线段上,为了判断上述情况是否发生,还需要如下一段代码判断一个点是否在一条线段上(不包含端点)
bool is_point_on_segment(Point P, Point A, Point B) {
    return dcmp(cross(A - P, B - P)) == 0 && dcmp(dot(A - P, B - P)) <= 0; // <= 0 保证端点也算,< 0 不算
}
1
2
3

# 多边形

把多边形从第一个顶点出发把凸多边形分成 n−2n-2n−2 个三角形,然后把面积加起来

其实这个方法对非凸多边形也适用,因为三角形的面积是有向的,在外面的部分可以抵消掉

# 多边形面积

double convex_polygon_area(vector<Point> &p) {
    int n = p.size();
    double area = 0;
    for (int i = 1; i < n - 1; i++) area += area2(p[0], p[i], p[i + 1]);
    return area / 2;
}
1
2
3
4
5
6

# 点在多边形内判定

我们定义一个多边形就是二维平面上被一系列首尾相连,闭合的折线段围成的区域,在程序种一般用顶点数组表示,各个点按逆时针顺序排列

有两种方法能判定

第一种是射线法

从某个判定点出发,任意引一条射线,如果和边界相交奇数次,说明在多边形内,如果相交都数次,说明在多边形外。不能再端点处和完整边处相交

第二种是转角度法

我们把多边形每条边转的角度加起来,如果是 360° 说明再多边形内,如果是 0°,说明再多边形外,如果是 180° 说明在多边形的边界上

一般我们使用第二种方法,直接按照定义去实现需要计算大量的反三角函数,容易产生精度问题。我们一般假象一条向右的射线,统计多边形穿过这条射线正反多少次,把这个记为绕数 cntcntcnt ,逆时针穿过时,cnt+1cnt+1cnt+1 ,顺时针穿过时 cnt−1cnt-1cnt−1

image.png

bool is_point_in_polygon(Point p, vector<Point> &poly) {
    int n = poly.size(), cnt = 0;
    for (int i = 0; i < n; i++) {
        Point u = poly[i], v = poly[(i + 1) % n];
        if (is_point_on_segment(p, u, v)) return true;
        double k = cross(v - u, p - u);
        double d1 = dcmp(u.y - p.y), d2 = dcmp(v.y - p.y);
        if (k > 0 && d1 <= 0 && d2 > 0) cnt++;
        if (k < 0 && d2 <= 0 && d1 > 0) cnt--;
    }
    return cnt != 0; // 0 外部,1 内部
}
1
2
3
4
5
6
7
8
9
10
11
12
上次更新: 2025/04/08, 18:03:31
博弈论
三维计算几何基础

← 博弈论 三维计算几何基础→

最近更新
01
Java基础语法
05-26
02
开发环境配置
05-26
03
pink 老师 JavaScript 学习笔记
05-26
更多文章>
Theme by Vdoing | Copyright © 2024-2025 Martian148 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式