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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
1. 前置知识点
(1) pi = acos(-1);
(2) 余弦定理 c^2 = a^2 + b^2 - 2abcos(t)

2. 浮点数的比较
const double eps = 1e-8;
int sign(double x) // 符号函数
{
if (fabs(x) < eps) return 0;
if (x < 0) return -1;
return 1;
}
int cmp(double x, double y) // 比较函数
{
if (fabs(x - y) < eps) return 0;
if (x < y) return -1;
return 1;
}

3. 向量
3.1 向量的加减法和数乘运算
3.2 内积(点积) A·B = |A||B|cos(C)
(1) 几何意义:向量A在向量B上的投影与B的长度的乘积。
(2) 代码实现
double dot(Point a, Point b)
{
return a.x * b.x + a.y * b.y;
}
3.3 外积(叉积) AxB = |A||B|sin(C)
(1) 几何意义:向量A与B张成的平行四边形的有向面积。B在A的逆时针方向为正。
(2) 代码实现
double cross(Point a, Point b)
{
return a.x * b.y - b.x * a.y;
}
3.4 常用函数
3.4.1 取模
double get_length(Point a)
{
return sqrt(dot(a, a));
}
3.4.2 计算向量夹角
double get_angle(Point a, Point b)
{
return acos(dot(a, b) / get_length(a) / get_length(b));
}
3.4.3 计算两个向量构成的平行四边形有向面积
double area(Point a, Point b, Point c)
{
return cross(b - a, c - a);
}
3.4.5 向量A顺时针旋转C的角度:
Point rotate(Point a, double angle)
{
return Point(a.x * cos(angle) + a.y * sin(angle), -a.x * sin(angle) + a.y * cos(angle));
}
4. 点与线
4.1 直线定理
(1) 一般式 ax + by + c = 0
(2) 点向式 p0 + vt
(3) 斜截式 y = kx + b
4.2 常用操作
(1) 判断点在直线上 A x B = 0
(2) 两直线相交
// cross(v, w) == 0则两直线平行或者重合
Point get_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;
}
(3) 点到直线的距离
double distance_to_line(Point p, Point a, Point b)
{
vector v1 = b - a, v2 = p - a;
return fabs(cross(v1, v2) / get_length(v1));
}
(4) 点到线段的距离
double distance_to_segment(Point p, Point a, Point b)
{
if (a == b) return get_length(p - a);
Vector v1 = b - a, v2 = p - a, v3 = p - b;
if (sign(dot(v1, v2)) < 0) return get_length(v2);
if (sign(dot(v1, v3)) > 0) return get_length(v3);
return distance_to_line(p, a, b);
}
(5) 点在直线上的投影
double get_line_projection(Point p, Point a, Point b)
{
Vector v = b - a;
return a + v * (dot(v, p - a) / dot(v, v));
}
(6) 点是否在线段上
bool on_segment(Point p, Point a, Point b)
{
return sign(cross(p - a, p - b)) == 0 && sign(dot(p - a, p - b)) <= 0;
}
(7) 判断两线段是否相交
bool segment_intersection(Point a1, Point a2, Point b1, Point b2)
{
double c1 = cross(a2 - a1, b1 - a1), c2 = cross(a2 - a1, b2 - a1);
double c3 = cross(b2 - b1, a2 - b1), c4 = cross(b2 - b1, a1 - b1);
return sign(c1) * sign(c2) <= 0 && sign(c3) * sign(c4) <= 0;
}
5. 多边形
5.1 三角形
5.1.1 面积
(1) 叉积
(2) 海伦公式
p = (a + b + c) / 2;
S = sqrt(p(p - a) * (p - b) * (p - c));
5.1.2 三角形四心
(1) 外心,外接圆圆心
三边中垂线交点。到三角形三个顶点的距离相等
(2) 内心,内切圆圆心
角平分线交点,到三边距离相等
(3) 垂心
三条垂线交点
(4) 重心
三条中线交点(到三角形三顶点距离的平方和最小的点,三角形内到三边距离之积最大的点)
5.2 普通多边形
通常按逆时针存储所有点
5.2.1 定义
(1) 多边形
由在同一平面且不再同一直线上的多条线段首尾顺次连接且不相交所组成的图形叫多边形
(2) 简单多边形
简单多边形是除相邻边外其它边不相交的多边形
(3) 凸多边形
过多边形的任意一边做一条直线,如果其他各个顶点都在这条直线的同侧,则把这个多边形叫做凸多边形
任意凸多边形外角和均为360°
任意凸多边形内角和为(n−2)180°
5.2.2 常用函数
(1) 求多边形面积(不一定是凸多边形)
我们可以从第一个顶点除法把凸多边形分成n − 2个三角形,然后把面积加起来。
double polygon_area(Point p[], int n)
{
double s = 0;
for (int i = 1; i + 1 < n; i ++ )
s += cross(p[i] - p[0], p[i + 1] - p[i]);
return s / 2;
}
(2) 判断点是否在多边形内(不一定是凸多边形)
a. 射线法,从该点任意做一条和所有边都不平行的射线。交点个数为偶数,则在多边形外,为奇数,则在多边形内。
b. 转角法
(3) 判断点是否在凸多边形内
只需判断点是否在所有边的左边(逆时针存储多边形)。
5.3 皮克定理
皮克定理是指一个计算点阵中顶点在格点上的多边形面积公式该公式可以表示为:
S = a + b/2 - 1
其中a表示多边形内部的点数,b表示多边形边界上的点数,S表示多边形的面积。
6.
(1) 圆与直线交点
(2) 两圆交点
(3) 点到圆的切线
(4) 两圆公切线
(5) 两圆相交面积

玩具

题意

一个盒子被分成了若干个区域,有m个小球,问每个区域里分别有多少个小球。

思考

我们发现首先我们必须要枚举每一个小球,复杂度高在每次需要判断有多少个隔板在小球左边,需要O(n)的时间,我们发现可以用二分查找小球应在的位置,将mid隔板是否在小球的左边为判断条件查找实际的位置,时间复杂度O(logn)。

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
66
67
68
69
#include <iostream>
#include <cstring>
#include <algorithm>

#define x first
#define y second

using namespace std;

typedef long long LL;
typedef pair<LL, LL> PLL;
const int N = 5010;

int n, m;
PLL a[N], b[N];
int ans[N];

LL cross(LL x1, LL y1, LL x2, LL y2)
{
return x1 * y2 - x2 * y1;
}

LL area(PLL a, PLL b, PLL c)
{
return cross(b.x - a.x, b.y - a.y, c.x - a.x, c.y - a.y);
}

int find(LL x, LL y)
{
int l = 0, r = n;
while (l < r)
{
int mid = l + r >> 1;
if (area(b[mid], a[mid], {x, y}) > 0) r = mid;
else l = mid + 1;
}
return r;
}

int main()
{
bool is_first = true;
while (scanf("%d", &n), n)
{
LL x1, y1, x2, y2;
scanf("%d%lld%lld%lld%lld", &m, &x1, &y1, &x2, &y2);
for (int i = 0; i < n; i ++ )
{
LL u, l;
scanf("%lld%lld", &u, &l);
a[i] = {u, y1}, b[i] = {l, y2};
}
a[n] = {x2, y1}, b[n] = {x2, y2};

if (is_first) is_first = false;
else puts("");
memset(ans, 0, sizeof ans);
while (m -- )
{
LL x, y;
scanf("%lld%lld", &x, &y);
ans[find(x, y)] ++ ;
}
for (int i = 0; i <= n; i ++ )
printf("%d: %d\n", i, ans[i]);
}

return 0;
}

总结

给定三个点,求三角形面积:用叉积。

1
2
3
4
5
6
7
8
9
LL cross(LL x1, LL y1, LL x2, LL y2)
{
return x1 * y2 - x2 * y1;
}

LL area(PLL a, PLL b, PLL c)
{
return cross(b.x - a.x, b.y - a.y, c.x - a.x, c.y - a.y);
}

线段

题意

在二维平面内有 n 条线段,请你编写一个程序,判断是否存在一条直线满足将这 n 条线段投影到该直线上后,所有的投影线段至少具有一个公共点。

思考

我们发现如果可以找到一个直线与所有的线段都相交,那么我们做一个垂直这个直线的垂线,所有的线段的投影一定都交于这个直线与垂线的垂足处。所以现在问题就变成了找一个可以穿过所有线段的直线。

这里我们使用一个计算几何常用的技巧:我们发现所有可能的答案,至少从与一个线段有交点开始。所以我们可以先找一个过一个线段的直线,然后我们旋转他(传统艺能)。我们把这个直线的定点定为这个线段的端点,然后旋转。因为我们要满足能够与其他的线段相交,所以我们旋转的时候有一个限制,就是不能超过另一个线段的一个端点,所以我们可以把所有的候选直线限定到所有线段的端点的连线的直线。

所以我们可以O(n^2),枚举所有线段的端点,然后O(n)暴力判断是否都与其他的线段有交点,如果存在一个直线与所有的线段有交点说明一定存在解,输出 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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

#define x first
#define y second

using namespace std;

typedef pair<double, double> PDD;
const int N = 210;
const double eps = 1e-8;

int n;
PDD q[N], a[N], b[N];

int sign(double x)
{
if (fabs(x) < eps) return 0;
if (x < 0) return -1;
return 1;
}

int cmp(double x, double y)
{
if (fabs(x - y) < eps) return 0;
if (x < y) return -1;
return 1;
}

double cross(double x1, double y1, double x2, double y2)
{
return x1 * y2 - x2 * y1;
}

double area(PDD a, PDD b, PDD c)
{
return cross(b.x - a.x, b.y - a.y, c.x - a.x, c.y - a.y);
}

bool check()
{
for (int i = 0; i < n * 2; i ++ )
for (int j = i + 1; j < n * 2; j ++ )
{
if (!cmp(q[i].x, q[j].x) && !cmp(q[i].y, q[j].y)) continue;
bool flag = true;
for (int k = 0; k < n; k ++ )
if (sign(area(q[i], q[j], a[k])) * sign(area(q[i], q[j], b[k])) > 0)
{
flag = false;
break;
}
if (flag) 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 ++ )
{
double x1, y1, x2, y2;
scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
q[k ++ ] = {x1, y1}, q[k ++ ] = {x2, y2};
a[i] = {x1, y1}, b[i] = {x2, y2};
}

if (check()) puts("Yes!");
else puts("No!");
}

return 0;
}

总结

判断两点是否位于直线两侧:直线上选两个点,分别和另外两个点构成两个三角形。它们的面积应异号。

1
sign(area(q[i], q[j], a[k])) * sign(area(q[i], q[j], b[k])) > 0