计算几何入门

前言

前置知识:初中数学,数学必修一,必修二部分内容,三角函数。

文中提到的矩阵啥的不用管,没什么用

向量

向量是一个有大小和方向的量,可以用有箭头的线段表示。若向量的起点为 A,终点为 B,则这个向量可以表示为 AB,或者 a

二位平面中充斥着无数的向量,通常为了方便表述一个向量,会将向量平移使得起点为原点,终点为 A(x,y),即一个点 A 表示向量 OA,可记为 [xy]

向量的模

向量的模长,即长度。向量 a 的模长记为 |a|,即 x2+y2

相反向量

方向相反,大小相等的向量为相反向量。向量 a 的相反向量记为为 a

垂直向量

若向量 ab 所在的直线相互垂直,则 ab 互为垂直向量。

共线向量

若向量 ab 所在的直线平行,则 ab 互为共线向量。

零向量

零向量的模长为 0,方向任意,它与任何一个向量都共线,坐标为 (0,0)

向量的加法

若将向量看成一种移动的话,那么如上图。先按 aO 走到 A 点,再按照 b 走到 B 点是相当于之间从 O 点走向 B 点的,即 c=a+b。若记 a=[x1y1],b=[x2y2],矩阵上的意义为

c=a+b=[x1y1]+[x2y2]=[x1+x2y1+y2]

向量的减法

可以将 ab 看成 a+(b),那么就可以用到加法了。记 c=ab,则 c 的起点为 b 的终点,c 的终点为 a 的终点,通常可以方便获得两个点之间的向量。例如上图中 BA=OAOB

向量的数乘

向量的数乘可记作 b=λaab 的模长之间的关系为 |b|=|λ||a|。若 λ>0,则 b 的方向与 a 相同;若 λ=0,则 b 为零向量;若 λ<0,则 b 的方向与 a 相反。

a=[xy],则矩阵上的意义可看作为

b=λa=λ[xy]=[λxλy]

向量的点积

点积,又名数量积,点乘,点积的几何意义为 a 的在 b 所在直线的投影的长度与 |b| 的乘积。记作 ab。通常可以省略中间的点乘。如上图,这里 ab=|a||b|cosa,b=OFOB。这里还是记作 a 的坐标为 (x1,y1)b 的坐标为 (x2,y2)。下面给个结论:ab=x1x2+y1y2

证明:

以上图为例,若 x 正半轴上有一点 C,记 AOB=θ,AOC=α,BOC=β

cosα=x1|a|,cosβ=x2|b|,sinα=y1|a|,sinβ=y2|b|

根据三角恒等变换有 cosθ=cos(αβ)=cosαcosβ+sinαsinβ

分别将 cosα,cosβ,sinα,sinβ 代入,得

cosθ=x1x2+y1y2|a||b|

得到 |a||b|cosa,b=x1x2+y1y2,命题得证。

显然点积是有交换律的,因为 cosα=cos(α)

点积可用于判断向量之间的前后关系。

  • 若两个向量共线且同向,那么它们的点积为他们的模长之积。

  • a,b<π2,则 ab>0

  • a,b=π2,则 ab=0

  • a,b>π2,则 ab<0

  • 若两个向量共线且反向,那么它们的点积为他们的模长之积的相反数。

如下图,直观的反应了夹角与点积的关系。

向量的叉积

又称外积,几何意义为向量根据平行四边形法则围成的面积。记为 a×b。根据几何意义,显然有 a×b=|a||b|sina,b。记作 a 的坐标为 (x1,y1)b 的坐标为 (x2,y2)。这里同样给个结论:a×b=x1y2x2y1

证明:

仿照点积的证明,我们同样在 x 正半轴上取一点 C,记 AOB=θ,AOC=α,BOC=β

sinβ=y2|b|,cosα=x1|a|,cosβ=x2|b|,sinα=y1|a|

根据三角恒等变换有 sinθ=sin(βα)=sinβcosαcosβsinα

分别将 sinβ,cosα,cosβ,sinα 代入,得

sinθ=x1y2x2y1|a||b|

得到 |a||b|sina,b=x1y2x2y1,命题得证。

另一种更为直观的证明:

如上图,我们可以用用割补法将蓝色部分的面积求出。颜色相同的面积是相等的,这个用全等不难证出,那么 a×b=(x1+x2)(y1+y2)x1y1x2y22x2y1=x1y2x2y1

叉积是没有交换律的,因为 sinαsin(α)

向量与矩阵之间有着密切的联系,叉积运算可看作是

a×b=|x1x2y1y2|

叉积可用于判断向量之间的左右关系。

  • 若两个向量共线,则 a×b=0

  • b 的终点在 a 的左侧,则 a×b>0

  • b 的终点在 a 的右侧,则 a×b<0

讲了这么多,向量的代码先放一下。

const double eps = 1e-8;//根据题目自选,通常这个好

int sgn(double x) { return fabs(x) < eps ? 0 : x > 0 ? 1 : -1; }//用于判断符号的

struct point {//向量,也可表示点
	double x, y;
	point(double x = 0, double y = 0) : x(x), y(y) {}
	point operator + (const point &b) const { return point(x+b.x, y+b.y); }//加法
	point operator - (const point &b) const { return point(x-b.x, y-b.y); }//减法
	point operator * (const double &k) const { return point(x*k, y*k); }//数乘,乘法
	point operator / (const double &k) const { return point(x/k, y/k); }//数乘,除法
};

double dot(point a, point b) { return a.x*b.x+a.y*b.y; }//点积
double crs(point a, point b) { return a.x*b.y-b.x*a.y; }//差积
double len(point a) { return sqrt(a.x*a.x+a.y*a.y); }//模长

线段交点

根据叉积我们可以求出线段的交点。

如上图,ABCD 与点 OA,B,C,D 的坐标已知,求点 O 的坐标。

先作 AECD 交于点 EBFCD 交于点 F

SCAD=AC×AD2,SCBD=BD×BC2

因为 CADCBD 共用 CD 这条底边。

所以 AEBF=AC×ADBD×BC

显然有 AOEBOF

所以 AOBO=AEBF=AC×ADBD×BC

所以 AO=AB×AC×ADAC×AD+BD×BC

即可求出 O

特别地,若 ABCD 没有交点,上述求的即为 AB 所在的直线与 CD 所在的直线的交点。也就是说,其实 ABCD 是否有交点我们是不关心的,读者可以自己画几个图推一推。

代码:

struct line {
	point s, e; double ang;//ang为线段与x轴的夹角
	line() {}
	line(point a, point b) { s = a, e = b, ang = atan2((b-a).y, (b-a).x); }
	bool operator < (const line &b) const { return sgn(ang-b.ang) ? ang < b.ang : sgn(crs(b.s-s, b.e-s)) > 0; }//极角排序
};

point get(line a, line b) { double x = crs(b.s-a.s, b.e-a.s), y = crs(b.e-a.e, b.s-a.e); return a.s+(a.e-a.s)*x/(x+y); }//求线段的交点

向量旋转

如图,有一向量 a 绕点 O 逆时针旋转 θ 度到向量 b,如何表示出 b 的坐标?

a 的坐标为 (x,y)l=x2+y2,那么有 x=lcosα,y=lsinα

B 的坐标为 (lcos(α+θ),lsin(α+θ))

根据三角恒等变换,有 cos(α+θ)=cosαcosθsinαsinθ,sin(α+θ))=sinαcosθ+cosαsinθ

x,y 带入进去,那么得 B 的坐标为 (xcosθysinθ,ycosθ+xsinθ)

代码:

point rotate(point a, double x) { return point(a.x*cos(x)-a.y*sin(x), a.y*cos(x)+a.x*sin(x)); }

三角剖分求面积

向量的叉积所求的面积是有向的,方向用正负表示。若给一个多边形,只要逆时针的把相邻两个点的叉积一次累加起来,就能得到这个多边形的面积,这是一个类似容斥的过程,下面举个例子,方便读者更好的体会这个过程。

对于多边形 ABCDEF,我们先求 OA×OB 的值,得到图中红色的面积(红色为负的,蓝色为正的)。

接下来算 B,C 的。

接下来算 C,D 的。

接下来算 D,E 的。

接下来算 E,F 的。

最后算 F,A 的,算法结束,累加的面积即为多边形的面积。

代码:

...//逆时针输入一个n个点的多边形 
for (int i = 1; i <= n; ++i) ans += crs(p[i], p[i == n ? 1 : i+1]);
printf("%.2lf", fabs(ans)/2);

凸包

想象一个平面有 n 个点,若用一条绳子将这 n 个点包起来,求出绳子的最小长度。

首先这条绳子所围成的多边形一定为凸多边形,否则根据三角形不等式得出一定不是最优的,可以直接连边,如下图所示:

那么下面介绍两个算法:

Graham 算法

第一步我们需要找到一个纵坐标最小的点(如有相同的取横坐标最小的),然后极角排序,接下来从第一个点开始扫描每个点。过程中需要维护一个栈,也是当前我们凸包已经选的点,若当前点与栈顶的点的连线与上一条方向不符时,那么去掉栈顶的点,直到符合要求时加入栈。扫描完毕时栈中的点集即为凸包的点。

下面是图解:

若干点。

选出 A 点,极角排序。

栈中加入 B 点,没什么问题。

栈中加入 D 点,没什么问题。

栈中加入 C 点,没什么问题。

栈中加入 E 点,不呈凸多边形,栈中排出 C

符合要求。

栈中加入 F 点,没什么问题。

栈中加入 G 点,不呈凸多边形,栈中排出 F

符合要求。

栈中加入 H 点,没什么问题。

最后算法结束,栈中为 A,B,D,E,G,H,为凸包的点(逆时针排列)。

代码:

#include <bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define mk make_pair
#define ll long long
#define space putchar(' ')
#define enter putchar('\n')
using namespace std;

typedef vector <int> vi;
typedef pair <int, int> pii;

inline int rd() { int x = 0, f = 1; char c = getchar(); while (!isdigit(c)) f = c == '-' ? -1 : f, c = getchar(); while (isdigit(c)) x = (x<<3)+(x<<1)+(c^48), c = getchar(); return x*f; }
inline ll rdll() { ll x = 0, f = 1; char c = getchar(); while (!isdigit(c)) f = c == '-' ? -1 : f, c = getchar(); while (isdigit(c)) x = (x<<3)+(x<<1)+(c^48), c = getchar(); return x*f; }
template <typename T> inline void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x/10); putchar(x%10+48); }

const double eps = 1e-8;
const int N = 1e5+5;

struct point {
	double x, y;
	point(double x = 0, double y = 0) : x(x), y(y) {}
	point operator + (const point &b) const { return point(x+b.x, y+b.y); }
	point operator - (const point &b) const { return point(x-b.x, y-b.y); }
	point operator * (const double &k) const { return point(x*k, y*k); }
	point operator / (const double &k) const { return point(x/k, y/k); }
} p[N], s[N];

double dot(point a, point b) { return a.x*b.x+a.y*b.y; }
double crs(point a, point b) { return a.x*b.y-b.x*a.y; }
int sgn(double x) { return fabs(x) < eps ? 0 : x > 0 ? 1 : -1; }
double len(point a) { return sqrt(a.x*a.x+a.y*a.y); }
double dist(point a, point b) { return len(a-b); }

bool cmp(point x, point y) { return sgn(crs(x-p[1],y-p[1])) > 0 ? 1 : sgn(crs(x-p[1], y-p[1])) < 0 ? 0 : dist(p[1], x) < dist(p[1], y); }

int main() {
	int n = rd(), top = 0;
	for (int i = 1; i <= n; ++i) {
		scanf("%lf%lf", &p[i].x, &p[i].y);
		if (i > 1 && (p[i].y < p[1].y || (p[i].y == p[1].y && p[i].x < p[1].x))) swap(p[1], p[i]);
	}
	sort(p+2, p+n+1, cmp);
	s[++top] = p[1];
	for (int i = 2; i <= n; ++i) {
		while (top > 1 && sgn(crs(s[top]-s[top-1], p[i]-s[top])) <= 0) --top;
		s[++top] = p[i];
	}
	s[++top] = p[1];
	double ans = 0;
	for (int i = 1; i < top; ++i) ans += dist(s[i], s[i+1]);
	printf("%.2lf", ans);
	return 0;
}

还在施工中 qwq……

posted @   123wwm  阅读(43)  评论(2编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示