计算几何
基础知识
y总总结知识点
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 + v*t (v是方向向量)(计算几何用的最多)(t的取值范围可以限制其为直线、射线、线段)
(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)
{
ector 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) 判断 a1a2 与 b1b2 两线段是否相交
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;
//即 b1, b2 在 a1 两侧且 a1, a2 在 b1 的两侧。
}
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. 转角法(在多边形内转360度,在多边形外转0度)
(3) 判断点是否在凸多边形内
只需判断点是否在所有边的左边(逆时针存储多边形)(每个边看作向量)。
5.3 皮克定理
皮克定理是指一个计算点阵中顶点在格点上的多边形面积公式该公式可以表示为:
S = a + b/2 - 1
其中a表示多边形内部的点数,b表示多边形边界上的点数,S表示多边形的面积。
必须保证多边形所有顶点的坐标都是整数。
6. 圆(一般都要联立解方程)
(1) 圆与直线交点
(2) 两圆交点
(3) 点到圆的切线
(4) 两圆公切线
(5) 两圆相交面积
- 有向面积:两个向量做叉积,得到的向量指向纸外是正值,指向纸内是负值。
- 点 A ( x , y ) A(x, y) A(x,y) 绕 O O O 顺时针旋转 θ \theta θ :
( x , y ) ∗ ( c o s θ − s i n θ s i n θ c o n θ ) (x, y) * \begin{pmatrix}cos\theta & -sin\theta\\sin\theta & con\theta\end{pmatrix} (x,y)∗(cosθsinθ−sinθconθ)
2983. 玩具
- 每当约翰将玩具扔进收纳盒中时,确定每个分区中有多少个玩具。
- 判断点在向量的左侧还是右侧,可以用向量叉积的正负来表示。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> P;
const int maxn = 5010;
int N, M;
P a[maxn], b[maxn];
int ans[maxn];
ll cross(ll x1, ll y1, ll x2, ll y2) {
return x1 * y2 - x2 * y1;
}
ll area(P a, P b, P c) {
return cross(c.first - a.first, c.second - a.second, b.first - a.first, b.second - a.second);
}
int find(int x, int y) {
int lb = 0, ub = N;
while (ub - lb > 0) {
int mid = (lb + ub) / 2;
if (area(b[mid], a[mid], { x, y }) < 0) ub = mid;
else lb = mid + 1;
}
return ub;
}
int main() {
ll x1, y1, x2, y2;
bool is_first = true;
while (cin >> N, N) {
cin >> M >> x1 >> y1 >> x2 >> y2;
memset(ans, 0, sizeof ans);
for (int i = 0; i < N; i++) {
scanf("%lld%lld", &a[i].first, &b[i].first);
a[i].second = y1, b[i].second = y2;
}
a[N] = { x2, y1 }, b[N] = { x2, y2 };
for (int i = 0; i < M; i++) {
ll x, y;
scanf("%lld%lld", &x, &y);
ans[find(x, y)]++;
}
if (!is_first) {
printf("\n");
}
else is_first = false;
for (int i = 0; i <= N; i++) {
printf("%d: %d\n", i, ans[i]);
}
}
return 0;
}
2984. 线段
- 在二维平面内有 n 条线段,请你编写一个程序,判断是否存在一条直线满足将这 n 条线段投影到该直线上后,所有的投影线段至少具有一个公共点。
- 只要能找到一条穿过所有线段的直线,那么做一条与这条直线垂直的直线,那这条垂直的直线一定满足要求。
- 用旋转直线的方式,那么它必然旋转着旋转着会卡在某一个端点上。换言之,我们只需要找到是否存在过所有线段至少一个端点的直线,就等价于能否找到上述直线。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
typedef pair<double, double> P;
const int maxn = 210;
const double EPS = 1e-8;
int N;
P q[maxn], a[maxn], b[maxn];
bool cmp(double x, double y) {
if (fabs(x - y) < EPS) return 0;
else if (x < y) return -1;
else return 1;
}
int sign(double x) {
if (fabs(x) < EPS) return 0;
else if (x < 0) return -1;
else return 1;
}
double cross(double x1, double y1, double x2, double y2) {
return x1 * y2 - x2 * y1;
}
double area(P a, P b, P c) {
return cross(a.first - c.first, a.second - c.second, b.first - c.first, b.second - c.second);
}
bool check() {
for (int i = 0; i < 2 * N; i++) {
for (int j = i + 1; j < 2 * N; j++) {
if (!cmp(q[i].first, q[j].first) && !cmp(q[i].second, q[j].second)) 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()) printf("Yes!\n");
else printf("No!\n");
}
}
凸包
- 凸包指的是周长最小,而不是面积最小。
1401. 围住奶牛
- Andrew 算法
- 将所有点排序,x 为第一关键字,y 为第二关键字。
- 从左至右维护上半部分,从右至左维护下半部分。如果这条边在栈顶元素构成向量的左侧,就把栈顶元素删掉,在右侧就留下来,如果在栈顶元素向量上,看题目要求。
- 题意:现在给定这些地点的具体坐标,请你求出将这些地点都包含在内的围栏的最短长度是多少(围栏边上的点也算处于围栏内部)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef pair<double, double> P;
const int maxn = 10010;
P q[maxn];
int stk[maxn], N;
bool used[maxn];
P operator - (P a, P b) {
return { a.first - b.first, a.second - b.second };
}
double cross(P a, P b) {
return a.first * b.second - b.first * a.second;
}
double area(P a, P b, P c) {
return cross(b - a, c - a);
}
double get_dist(P a, P b) {
double dx = a.first - b.first, dy = a.second - b.second;
return sqrt(dx * dx + dy * dy);
}
double Andrew() {
sort(q, q + N);
int top = 0;
for (int i = 0; i < N; i++) {
while (top >= 2 && area(q[stk[top]], q[stk[top - 1]], q[i]) >= 0) {
// 凸包边界上的点即使被从栈中删掉,也不能删掉used上的标记
if (area(q[stk[top - 1]], q[stk[top]], q[i]) < 0)
used[stk[top--]] = false;
else top--;
}
used[i] = true;
stk[++top] = i;
}
used[0] = false;
for (int i = N - 1; i >= 0; i--) {
if (used[i]) continue;
while (top >= 2 && area(q[stk[top]], q[stk[top - 1]], q[i]) >= 0) {
top--;
}
stk[++top] = i;
}
double res = 0;
for (int i = 2; i <= top; i++) {
res += get_dist(q[stk[i - 1]], q[stk[i]]);
}
return res;
}
int main() {
scanf("%d", &N);
for (int i = 0; i < N; i++) scanf("%lf%lf", &q[i].first, &q[i].second);
double ans = Andrew();
printf("%.2f\n", ans);
return 0;
}
2935. 信用卡凸包
- 题意:信用卡是一个矩形,唯四个角作了圆滑处理,使它们都是与矩形的两边相切的 1 4 \frac{1}{4} 41 圆。现在平面上有一些规格相同的信用卡,试求其凸包的周长。注意凸包未必是多边形,因为它可能包含若干段圆弧。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PIQK8Uk4-1686468593882)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20210124084732974.png)]
- 圆心角的大小等于两切线的夹角。这个性质要用好。
- 观察可发现,所有圆心构成凸包,加上圆弧长度就是答案。而圆弧的长度是有圆心角之和决定,我们发现圆心角之和等于凸包的外角之和。任意凸多边形外角和是360度。因此凸包长度加上 2 π r 2\pi r 2πr 即可。
- 注意这个题绕中心旋转的处理,是现在原点旋转,再平移。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define x first
#define y second
const int maxn = 40010;
const double PI = acos(-1);
typedef pair<double, double> P;
int N, cnt;
P q[maxn];
int stk[maxn], top;
bool used[maxn];
P rotate(P a, double b) {
return { a.x * cos(b) + a.y * sin(b), -a.x * sin(b) + a.y * cos(b) };
}
P operator - (P a, P b) {
return { a.x - b.x, a.y - b.y };
}
double cross(P a, P b) {
return a.x * b.y - b.x * a.y;
}
double area(P a, P b, P c) {
return cross(b - a, c - a);
}
double get_dist(P a, P b) {
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
double andrew() {
sort(q, q + cnt);
int top = 0;
for (int i = 0; i < cnt; i++) {
while (top >= 2 && area(q[stk[top - 1]], q[stk[top]], q[i]) <= 0) {
if (area(q[stk[top - 1]], q[stk[top]], q[i]) < 0)
used[stk[top--]] = false;
else top--;
}
stk[++top] = i;
used[i] = true;
}
used[0] = false;
for (int i = cnt - 1; i >= 0; i--) {
if (used[i]) continue;
while (top >= 2 && area(q[stk[top - 1]], q[stk[top]], q[i]) <= 0) {
top--;
}
stk[++top] = i;
}
double res = 0;
for (int i = 2; i <= top; i++) {
res += get_dist(q[stk[i - 1]], q[stk[i]]);
}
return res;
}
int main() {
cin >> N;
double a, b, r;
cin >> a >> b >> r;
a = a / 2 - r, b = b / 2 - r;
int dx[] = { 1, 1, -1, -1 }, dy[] = { 1, -1, 1, -1 };
while (N--) {
double x, y, z;
scanf("%lf%lf%lf", &x, &y, &z);
for (int i = 0; i < 4; i++) {
auto t = rotate({ dx[i] * b, dy[i] * a }, -z);
q[cnt++] = { t.x + x, t.y + y };
}
}
double res = andrew();
printf("%.2f\n", res + 2 * PI * r);
}
半平面交
- 给一些有向直线,每次只保留直线左侧部分的平面。凸多边形砍一刀仍是凸多边形。
- 如果题目强行要求保留右半部分,只需要把直线方向反过来就行。
- 步骤
- 先将所有向量按角度排序 atan2(y, x);如果角度相同,则靠左边的直线优先。
- 按顺序扫描所有向量,用双端队列维护。主要就是判端队列中的交点是否在直线右侧。
2803. 凸多边形
- 题意:逆时针给出 n 个凸多边形的顶点坐标,求它们交的面积。
- 输入所有点,点两两按顺序形成直线,全部保存在一个数组中,然后对这些点做半平面交。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn = 510;
#define x first
#define y second
typedef pair<double, double> P;
int cnt;
struct Line {
P st, ed;
}line[maxn];
int q[maxn]; //双端队列
P pg[maxn], ans[maxn];
//(1)先摆上三个常用函数
P operator -(P a, P b) {
return { a.x - b.x, a.y - b.y };
}
double cross(P a, P b) {
return a.x * b.y - a.y * b.x;
}
double area(P a, P b, P c) {
return cross(b - a, c - a);
}
//(2)求直线的交点
P get_line_intersection(P p, P v, P q, P w) {
auto u = p - q;
double t = cross(w, u) / cross(v, w);
return { p.x + v.x * t, p.y + v.y * t };
}
P get_line_intersection(Line a, Line b) {
return get_line_intersection(a.st, a.ed - a.st, b.st, b.ed - b.st);
}
//(3)写直线的比较函数
const double EPS = 1e-8;
int sign(double x) {
if (fabs(x) < EPS) return 0;
else if (x < 0) return -1;
else return 1;
}
int dcmp(double x, double y) {
if (fabs(x - y) < EPS) return 0;
else if (x < y) return -1;
else return 1;
}
double get_angle(const Line& a) {
//求直线倾斜角 (-PI, PI].
return atan2(a.ed.y - a.st.y, a.ed.x - a.st.x);
}
bool cmp(const Line& a, const Line& b) {
double A = get_angle(a), B = get_angle(b);
if (!dcmp(A, B)) return area(a.st, a.ed, b.ed) < 0;
return A < B;
}
bool on_right(Line& a, Line& b, Line& c) {
//直线 b 和直线 c 的交点在直线 a 的右侧。
auto o = get_line_intersection(b, c);
return sign(area(o, a.st, a.ed)) <= 0;
}
double half_plane_intersection() {
sort(line, line + cnt, cmp);
int hh = 0, tt = -1; //q[hh]表示队头,q[tt]表示队尾, tt - hh + 1 表示队列的大小。
for (int i = 0; i < cnt; i++) {
if (i && dcmp(get_angle(line[i]), get_angle(line[i - 1])) == 0) continue; //角度相同,只保留左边的直线即可。
//队列多于两个元素时,若队尾两直线交点在新的直线右边,则删除队尾元素。
while (tt - hh >= 1 && on_right(line[i], line[q[tt]], line[q[tt - 1]])) tt--;
//同样,对于队头,维护同样的操作。
while (tt - hh >= 1 && on_right(line[i], line[q[hh]], line[q[hh + 1]])) hh++;
q[++tt] = i;
}
//再用队尾更新队头,用队头更新队尾。
while (tt - hh >= 1 && on_right(line[q[hh]], line[q[tt - 1]], line[q[tt]])) tt--;
while (tt - hh >= 1 && on_right(line[q[tt]], line[q[hh]], line[q[hh + 1]])) hh++;
q[++tt] = q[hh];
int k = 0;
//注意,最有一个元素是第一个元素,因此要 i < tt;
for (int i = hh; i < tt; i++) {
ans[k++] = get_line_intersection(line[q[i]], line[q[i + 1]]);
}
double res = 0;
for (int i = 1; i + 1 < k; i++) {
res += area(ans[0], ans[i], ans[i + 1]);
}
//注意,一定是 res / 2,因为求的是三角形的有向面积,cross 函数求的是两向量构成的平行四边形的有向面积。
return res / 2;
}
int main() {
int N, M;
scanf("%d", &N);
while (N--) {
scanf("%d", &M);
for (int i = 0; i < M; i++) {
scanf("%lf%lf", &pg[i].x, &pg[i].y);
}
for (int i = 0; i < M; i++) {
line[cnt++] = { pg[i], pg[(i + 1) % M] };
}
}
double res = half_plane_intersection();
printf("%.3f\n", res);
return 0;
}
最小圆覆盖
- 最小覆盖圆是唯一的,这个可以用反证法去证。
- 若 P 不在 S 的最小覆盖圆的内部,则 P 一定在 { P } ∪ S \{P\} \cup S {P}∪S 的最小覆盖圆的边上。
- 求三个点的外接圆
3028. 最小圆覆盖
- 在一个二维平面上给定 N 个点,请你画出一个最小的能够包含所有点的圆。圆的边上的点视作在圆的内部。
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
using namespace std;
#define x first
#define y second
const int maxn = 100010;
const double eps = 1e-12;
const double PI = acos(-1);
typedef pair<double, double> P;
typedef pair<P, P> Line;
int N;
P q[maxn];
//圆的结构体
struct Circle {
P p;
double r;
};
int sign(double x) {
if (fabs(x) < eps) return 0;
if (x < 0) return -1;
return 1;
}
int dcmp(double x, double y) {
if (fabs(x - y) < eps) return 0;
if (x < y) return -1;
return 1;
}
P operator -(P a, P b) {
return { a.x - b.x, a.y - b.y };
}
P operator +(P a, P b) {
return { a.x + b.x, a.y + b.y };
}
P operator *(P a, double t) {
return { t * a.x, t * a.y };
}
P operator /(P a, double t) {
return { a.x / t, a.y / t };
}
double cross(P a, P b) {
return a.x * b.y - a.y * b.x;
}
double get_dist(P a, P b) {
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
//求两直线交点
P get_line_intersection(P p, P v, P q, P w) {
auto u = p - q;
double t = cross(w, u) / cross(v, w);
return p + v * t;
}
//求中垂线
P rotate(P a, double b) {
return { a.x * cos(b) + a.y * sin(b), -a.x * sin(b) + a.y * cos(b) };
}
Line get_line(P a, P b) {
return { (a + b) / 2, rotate(b - a, PI / 2) };
}
//求外接圆
Circle get_circle(P a, P b, P c) {
auto u = get_line(a, b), v = get_line(a, c);
auto o = get_line_intersection(u.x, u.y, v.x, v.y);
return { o, get_dist(o, a) };
}
int main() {
scanf("%d", &N);
for (int i = 0; i < N; i++) scanf("%lf%lf", &q[i].x, &q[i].y);
random_shuffle(q, q + N);
Circle c = { q[0] , 0 };
for (int i = 1; i < N; i++) {
if (dcmp(c.r, get_dist(c.p, q[i])) < 0) {
c = { q[i], 0 };
for (int j = 0; j < i; j++) {
if (dcmp(c.r, get_dist(c.p, q[j])) < 0) {
c = { (q[i] + q[j]) / 2, get_dist(q[i], q[j]) / 2 };
for (int k = 0; k < j; k++) {
if (dcmp(c.r, get_dist(c.p, q[k])) < 0) {
//三点确定一个圆
c = get_circle(q[i], q[j], q[k]);
}
}
}
}
}
}
printf("%.10f\n", c.r);
printf("%.10f %.10f\n", c.p.x, c.p.y);
return 0;
}
2785. 信号增幅仪
- 题意:无线网络基站在理想状况下有效信号覆盖范围是个圆形,而无线基站的功耗与圆的半径的平方成正比。增幅仪能够在不增加无线基站功耗的前提下,使得有效信号的覆盖范围在某一特定方向上伸长若干倍。
- 先把所有点旋转(注意与增幅仪旋转方向相反)就相当于椭圆与坐标轴平行了。此题椭圆的方程为 x 2 p 2 + y 2 = r 2 \frac{x^2}{p^2} + y^2 = r^2 p2x2+y2=r2. 因此,我们再把所有点的横坐标都变为 x ′ = x p x' = \frac{x}{p} x′=px,就变成了典型的最小圆覆盖问题。
代码和上一道题没啥区别,略。
三维计算几何基础与三维凸包
基础知识:
1. 三维向量表示(x, y, z)
2. 向量加减法、数乘运算,与二维相同
3. 模长 |A| = sqrt(x * x + y * y + z * z)
4. 点积
(1) 几何意义:A·B = |A| * |B| * cos(C)
(2) 代数求解:(x1, y1, z1) · (x2, y2, z2) = (x1x2, y1y2, z1z2);
5. 叉积
(1) 几何意义:AxB = |A| * |B| * sin(C),方向:右手定则
(2) 代数求解:AxB = (y1z2 - z1y2, z1x2 - x1z2, x1y2 - y1x2)
6. 如何求平面法向量
任取平面上两个不共线的向量A、B:AxB
7. 判断点D是否在平面里
任取平面上两个不共线的向量A、B:先求法向量C = AxB,然后求平面上任意一点到D的向量E与C的点积,判断点积是否为0。
8. 求点D到平面的距离
任取平面上两个不共线的向量A、B:先求法向量C = AxB。然后求平面上任意一点到D的向量E在C上的投影长度即可。即:E·C / |C|
9. 多面体欧拉定理
顶点数 - 棱长数 + 表面数 = 2
10. 三维凸包
2119. 最佳包裹
- 题意:输入包括该产品的顶点的个数,以及所有顶点的坐标;请计算出包裹这个产品所需要的材料的最小面积
- 其实就是求三维凸包,通常是一个 O ( n 2 ) O(n^2) O(n2) 的暴力求法。
- 用逆时针方向三个点维护一个面。加入了随机扰动,防止四个点共面的情况,因此就不用加入近似的判断了,比如 sign 与 dcmp 函数。这样可以有效防止四点共面。
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn = 110;
const double eps = 1e-12;
int N, M; //点的总数,三维凸包面的个数
bool g[maxn][maxn]; //记录每个面是否被照到。g[i][j] 表示 i 和 j 相连的边。
double rand_eps() {
return ((double)rand() / RAND_MAX - 0.5) * eps;
}
struct Point {
double x, y, z;
void shake() {
x += rand_eps(), y += rand_eps(), z += rand_eps();
}
Point operator-(Point t) {
return { x - t.x, y - t.y, z - t.z };
}
Point operator+(Point t) {
return { x + t.x, y + t.y, z + t.z };
}
//点乘
double operator&(Point t) { //小心优先级的问题,因为&优先级挺低的。
return x * t.x + y * t.y + z * t.z;
}
//叉乘
Point operator*(Point t) {
return { y * t.z - t.y * z, z * t.x - x * t.z, x * t.y - y * t.x };
}
double len() {
return sqrt(x * x + y * y + z * z);
}
}q[maxn];
struct Plane {
int v[3];
Point norm() {
return (q[v[1]] - q[v[0]]) * (q[v[2]] - q[v[0]]);
}
double area() {
return norm().len() / 2;
}
bool above(Point a) {
return ((a - q[v[0]]) & norm()) >= 0; //一定要加括号,不然优先级会出问题!
}
}plane[maxn], np[maxn]; //开一个备份数组
void get_convex_3d() {
plane[M++] = { 0, 1, 2 };
plane[M++] = { 2, 1, 0 };
for (int i = 3; i < N; i++) {
int cnt = 0;
for (int j = 0; j < M; j++) {
bool t = plane[j].above(q[i]);
if (!t) np[cnt++] = plane[j];
for (int k = 0; k < 3; k++) {
g[plane[j].v[k]][plane[j].v[(k + 1) % 3]] = t;
}
}
for (int j = 0; j < M; j++) {
for (int k = 0; k < 3; k++) {
int a = plane[j].v[k], b = plane[j].v[(k + 1) % 3];
if (g[a][b] && !g[b][a]) { //意味着q[i] 只能照到 plane[j] 的一个面
np[cnt++] = { a, b, i };
}
}
}
M = cnt;
for (int j = 0; j < M; j++) plane[j] = np[j];
}
}
int main() {
scanf("%d", &N);
for (int i = 0; i < N; i++) {
scanf("%lf%lf%lf", &q[i].x, &q[i].y, &q[i].z);
q[i].shake();
}
get_convex_3d();
double res = 0;
for (int i = 0; i < M; i++) {
res += plane[i].area();
}
printf("%.6f\n", res);
return 0;
}