Dyd's Blog

He who has a strong enough why can bear almost any how.

计算几何基础知识

“这不显然吗”

计算几何基础知识

浮点数

  1. $\pi = \arccos(-1)$

  2. 余弦定理:$\triangle ABC$ 中 $c^2 = a^2 + b^2 - 2 a b \cos C$

  3. 浮点数比较:

    1
    2
    3
    4
    5
    6
    int cmp(double x, double y)
    {
    if (fabs(x - y) < eps)
    return 0;
    return x > y ? 1 : -1;
    }

    判断一个浮点数的符号就是 cmp(x, 0)

向量

如果不清楚可以看这个

  1. 向量的表示:一般用点 $(x, y)$ 表示向量 $(0, 0) \to (x, y)$ (把起点平移到原点)

  2. 加减法:已知 $\vec{a} = (x_a, y_a), \vec{b} = (x_b, y_b)$ ,则有 $\vec{a} \pm \vec{b} = (x_a \pm x_b, y_a \pm y_b)$ ,其几何意义就是平行四边形定则

  3. 数乘:已知 $\vec{a} = (x, y)$ 和标量 $b$ ,则 $b\vec{a} = (bx, by)$

  4. 模:向量的长度,定义为已知 $\vec{a} = (x, y)$ ,则模为 $|\vec{a}| = \sqrt{x^2 + y^2}$ ,模长是标量,代码中模长用内积来求

    1
    2
    3
    4
    double mo(Point x)
    {
    return sqrt(x & x);
    }
  5. 内积(点积):一个向量在另一个向量上的投影长度乘上另一个向量的长度,即已知 $\vec{a} = (x_a, y_a), \vec{b} = (x_b, y_b)$ ,则 $\vec{a} \cdot \vec{b} = |a| |b| \cos<\vec{a}, \vec{b}> = x_a x_b + y_a y_b$ ,是标量

    1
    2
    3
    4
    double operator & (Point x, Point y)
    {
    return x.x * y.x + x.y * y.y;
    }
  6. 外积(叉积):两个向量围成的平行四边形面积,定义为 $\vec{a} \times \vec{b} = |a| |b| \sin<\vec{a}, \vec{b}>$ ,是向量,方向用右手定则判定,四指从始向量指向末向量,大拇指朝上(朝自己)是正

    1
    2
    3
    4
    double operator * (Point x, Point y)
    {
    return x.x * y.y - y.x * x.y;
    }
  7. 计算向量夹角

    1
    2
    3
    4
    double angle(Point x, Point y)
    {
    return acos((x & x) / mo(x) / mo(y));
    }
  8. 向量(点)顺时针旋转角度:

    1
    2
    3
    4
    Point rot(Point x, double theta)
    {
    return {x.x * cos(theta) + x.y * sin(theta), -x.x * sin(theta) + x.y * cos(theta)};
    }
  9. 计算向量 $\vec{AB}, \vec{AC}$ 围成的平行四边形面积:

    1
    2
    3
    4
    double area(Point x, Point y, Point z)
    {
    return cross(y - x, z - x);
    }

点与线

注意下面的 Point 有些表示点,有些表示向量

  1. 直线的表示:

    • 一般式: $l : Ax + By + C = 0$
    • 斜截式: $l : y = kx + b$
    • 点向式: $l: p_0 + t\vec{v}$
    • 两点式:不多说,两点定一条直线

    一般使用点向式或两点式

  2. 判断点于直线关系:

    设点 $C$ 与直线 $AB$ ,令 $\vec{v} = \vec{AB} \times \vec{AC}$

    • 左侧: $\vec{v} > 0$

    • 右侧: $\vec{v} < 0$

    • 线上: $\vec{v} = 0$

  3. 直线交点,直接上代码了,证明可以考虑平移至共起点后用几何意义:

    1
    2
    3
    4
    5
    6
    7
    Point jiao(Point p, Point u, Point q, Point v)
    {
    if (u * v == 0) //平行或者重合
    return {INF, INF};
    double t = (p - q) * v / (u * w);
    return p + u * t;
    }
  4. 点到直线距离:

    1
    2
    3
    4
    5
    double dis_z(Point p, Point x, Point y) //p到直线(x->y)的距离
    {
    Point u = y - x, v = p - x;
    return fabs((u * v) / mo(u));
    }
  5. 点到线段的距离:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    double dis_x(Point p, Point x, Point y)
    {
    if (x == y)
    return mo(p - x);
    Point u = y - x, v = p - x, w = p - y;
    if (cmp(u & v, 0) < 0)
    return mo(v);
    if (cmp(u & w, 0) > 0)
    return mo(w);
    return dis_z(p, x, y);
    }
  6. 点在直线上的投影:

    1
    2
    3
    4
    5
    double proj(Point p, Point x, Point y)
    {
    Point u = y - x;
    return x + u * (u & (p - x)) / (u & u));
    }
  7. 判断点是否在直线上:

    1
    2
    3
    4
    bool is_on(Point p, Point x, Point y)
    {
    return !cmp((p - x) * (p - y), 0) && cmp((p - x) & (p - y), 0) <= 0;
    }
  8. 判断两线段是否相交:

    1
    2
    3
    4
    5
    6
    bool is_jiao(Point x_1, Point y_1, Point x_2, Point y_2)
    {
    double c1 = cross(y_1 - x_1, x_2 - x_1), c2 = cross(y_1 - x_1, y_2 - x_1);
    double c3 = cross(y_2 - x_2, y_1 - x_2), c4 = cross(y_2 - x_2, x_1 - x_2);
    return cmp(c1, 0) * cmp(c2, 0) <= 0 && cmp(c3, 0) * cmp(c4, 0) <= 0;
    }

三角形

  1. 海伦公式

  2. 四心:

    • 外心,外接圆圆心,三边中垂线交点,到三角形三个顶点的距离相等
    • 内心,内切圆圆心,角平分线交点,到三边距离相等
    • 垂心,三条垂线交点
    • 重心,三条中线交点(到三角形三顶点距离的平方和最小的点,三角形内到三边距离之积最大的点)

多边形

通常按逆时针存储所有点

  1. 凸多边形:

    过多边形的任意一边做一条直线,如果其他各个顶点都在这条直线的同侧,则把这个多边形叫做凸多边形,任意凸多边形外角和均为 $360$ 度,任意凸多边形内角和为 $(n−2) 180$ 度

  2. 求多边形面积(不一定是凸多边形):

    考虑在平面上任取一原点(一般取第一个点),对于边每条边 $AB$ (逆时针储存),计算 $\vec{OA} \times \vec{OB}$ (注意外积是有向的),求和即可,可画图理解

    area

    1
    2
    3
    4
    5
    6
    7
    double pgarea(Point p[], int n) //polygon area
    {
    double res = 0;
    for (int i = 1; i < n - 1; ++i) //这里点编号为0~n-1
    res += (p[i] - p[0]) * (p[i + 1] - p[0]);
    return res / 2;
    }
  3. 判断点是否在多边形内(不一定是凸多边形):

    • 射线法,从该点任意做一条和所有边都不平行的射线,交点个数为偶数,则在多边形外,为奇数,则在多边形内
    • 转角法
  4. 判断点是否在凸多边形内,只需判断点是否在所有边的左边(逆时针存储多边形)

  5. 皮克定理:

    皮克定理是指,一个计算点阵中顶点在格点上的多边形面积公式,该公式可以表示为 $S = a + \frac{b}{2} - 1$ ,其中 $a$ 表示多边形内部的点数, $b$ 表示多边形边界上的点数, $S$ 表示多边形的面积

解方程即可

三维

  1. 三维的向量表示 $(x, y, z)$

  2. 三维向量的加减法、数乘运算、点积运算,都与二维的相同,直接套

  3. 向量的模 $|\vec{V}| = \sqrt{x^2 + y^2 + z^2}$

  4. 叉积: $\vec{a} \times \vec{b}$ 是一个即垂直于 $\vec{a}$ 也垂直于 $\vec{b}$ 的向量,方向也用右手定则,代数式为 $\vec{a} \times \vec{b} = (y_az_b - y_bz_a, x_bz_a - x_az_b, x_ay_b - x_by_a)$

  5. 求法向量:

    根据叉积的性质,在平面里找两个不共线的向量,求他俩的叉积就是平面的法向量

  6. 判定点是否在平面上:

    先求出法向量 $n$ ,然后在平面里任取一点 $p$ ,判断 $(p \to x) * n$ 若为 $0$ 即在平面里

  7. 求点到平面的距离:

    先求出法向量 $n$ ,然后在平面里任取一点 $p$ , $(p \to x)$ 在 $n$ 上的投影即为距离

    $D = \frac{\vec{px} * \vec{n}}{|n|}$

  8. 多面题欧拉定理:

    多面体的顶点数 - 棱长数 + 表面数 = $2$

例题一

玩具

很简单的例题,直接二分每个点在那两个向量之间即可

判断点是否在直线左侧即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 5000 + 5;
int n;
struct Point
{
int x, y;
Point operator - (const Point &t) const
{
return {x - t.x, y - t.y};
}
} a[N], b[N];
int ans[N];
LL cross(Point u, Point v)
{
return (LL)u.x * v.y - (LL)v.x * u.y;
}
LL area(Point x, Point y, Point z)
{
return cross(y - x, z - x);
}
int find(int x, int y)
{
int l = 0, r = n, res = n;
while (l <= r)
{
int mid = l + r >> 1;
if (area(b[mid], a[mid], {x, y}) > 0)
res = mid, r = mid - 1;
else
l = mid + 1;
}
return res;
}
int main()
{
bool f = true;
int m;
while (scanf("%d", &n), n)
{
int x_1, y_1, x_2, y_2;
scanf("%d%d%d%d%d", &m, &x_1, &y_1, &x_2, &y_2);
for (int i = 0, u, v; i < n; ++i)
{
scanf("%d%d", &u, &v);
a[i] = {u, y_1}, b[i] = {v, y_2};
}
a[n] = {x_2, y_1}, b[n] = {x_2, y_2};
if (f)
f = false;
else
puts("");
memset(ans, 0, sizeof ans);
while (m--)
{
int x, y;
scanf("%d%d", &x, &y);
++ans[find(x, y)];
}
for (int i = 0; i <= n; ++i)
printf("%d: %d\n", i, ans[i]);
}
return 0;
}

例题二

线段

首先题目可以转化为,求一条过所有线段的直线

考虑枚举每条线段的端点,可以证明,如果存在这样的直线,必然有一条是穿过线段端点中的两个的

证明如下:

假设有一条直线符合题意,现在设其与线段 $l_1$ 的交点为 $A$ ,将直线绕 $A$ 旋转至“即将”不满足题意,此时其必过某条线段端点,再绕该端点旋转一次即可

时间复杂度 $O(Tn^3)$ 在答案为 $YES!$ 的情况下跑不满

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <bits/stdc++.h>
#define DB double
using namespace std;
const int N = 100 + 5;
const double eps = 1e-8;
int n;
struct Point
{
DB x, y;
Point operator - (const Point &t) const
{
return {x - t.x, y - t.y};
}
} a[N], b[N], q[N << 1];
int cmp(DB x, DB y)
{
if (fabs(x - y) < eps)
return 0;
return x > y ? 1 : -1;
}
DB cross(Point x, Point y)
{
return x.x * y.y - y.x * x.y;
}
DB area(Point x, Point y, Point z)
{
return cross(y - x, z - x);
}
bool check()
{
for (int i = 0; i < (n << 1); ++i)
for (int j = i + 1; j < (n << 1); ++j)
{
if (!cmp(q[i].x, q[j].x) && !cmp(q[i].y, q[j].y))
continue;
bool f = true;
for (int k = 0; k < n; ++k)
if (cmp(area(q[i], q[j], a[k]), 0) * cmp(area(q[i], q[j], b[k]), 0) > 0)
{
f = false;
break;
}
if (f)
return true;
}
return false;
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
scanf("%d", &n);
for (int i = 0, k = 0; i < n; ++i)
{
scanf("%lf %lf %lf %lf", &a[i].x, &a[i].y, &b[i].x, &b[i].y);
q[k++] = a[i], q[k++] = b[i];
}
check() ? puts("Yes!") : puts("No!");
}
return 0;
}