计算几何基础

挺不错的入门基础
进阶版
计算几何基础干货全家桶

基础知识

1.计算误差

  • 尽量少用三角函数、除法、开方、求幂、取对数运算
  • 尽量把公式整理成使用的以上操作最少的形式
  • 在不溢出的情况下将除式比较转化为乘式比较: a b > c ⇒ a > b c   ( b > 0 ) \frac{a}{b}>c \Rightarrow a > bc \ (b > 0) ba>ca>bc (b>0)
  • 在使用除法、开根号、和三角函数的时候,我们要考虑由浮点误差运算产生的代价,例如在开根号时加一个 e p s eps eps

2.判断相等

不要直接用等号判断浮点数是否相等!同样的一个数 1.5 1.5 1.5 如果从不同的途径计算得来,它们的储存方式可能会是 a = 1.4999999 a = 1.4999999 a=1.4999999 b = 1.5000001 b = 1.5000001 b=1.5000001,此时使用 a = = b a = = b a==b 判断将返回 f a l s e f a l s e false
可以通过以下方式来解决:
(1) 借助 e p s eps eps 判断

const double eps = 1e-9;
int dcmp(double x, double y){
    if(fabs(x - y) < eps)
        return 0;
    if(x > y)
        return 1;
    return -1;
}

(2) 化浮为整
在不溢出整数范围的情况下,可以通过乘上 1 0 k 10^k 10k 转化为整数运算,最后再将结果转化为浮点数输出

3.负零

输出时一定要小心不要输出 − 0 − 0 0,比如

double a = -0.000001;
printf("%.4f", a);

4.反三角函数

使用反三角函数时,要注意定义域的范围,比如,经过计算 x = 1.000001 x = 1.000001 x=1.000001
round函数

double x = 1.000001;
double acx = acos(x);
//可能会返回runtime error
//此时我们可以加一句判断
double x = 1.000001;
if(fabs(x - 1.0) < eps || fabs(x + 1.0) < eps)
	x = round(x);
double acx = acos(x);

向量

struct Point{
    double x, y;
    Point(double x = 0, double y = 0) : x(x), y(y){}
};
向量
  • 既有大小又有方向的量叫做向量
  • 在计算机中我们常用坐标表示
typedef Point Vector;
运算

加法

  • 点与点之间的加法运算没有意义
  • 点与向量相加得到另一个点
  • 向量与向量相加得到另外一个向量

减法

  • 两个点之间作差将得到一个向量
  • 向量 a a a 减向量 b b b,得到的是向量 b b b 的终点指向向量 a a a 终点的一个向量

内积

又称数量积,点积
α ⋅ β = ∣ α ∣ ∣ β ∣ c o s θ \alpha · \beta=|\alpha||\beta|cos\theta αβ=αβcosθ
θ \theta θ 表示向量 α \alpha α β \beta β 的夹角

外积

又称向量积,叉积
α × β = ∣ α ∣ ∣ β ∣ s i n θ \alpha \times \beta=|\alpha||\beta|sin\theta α×β=αβsinθ
θ \theta θ 表示向量 α \alpha α 旋转到 β \beta β 经过的角度
几何意义:向量 α \alpha α β \beta β 所张称的四边形的面积
β \beta β α \alpha α 逆时针方向,则为正值,顺时针方向为负值,共线为 0 0 0

板子

struct Point{
    double x, y;
    Point(double x = 0, double y = 0):x(x),y(y){}
};
typedef Point Vector;
const double eps = 1e-6;
int sgn(double x){// 判断实数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;
}
// 向量,重载运算符
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);
}
bool operator < (const Point& a, const Point& b){//Left Then Low 排序
    if(a.x == b.x)
        return a.y < b.y;
    return a.x < b.x;
}
bool operator == (const Point& a, const Point& b){// 判断向量等于
    if(sgn(a.x-b.x) == 0 && sgn(a.y-b.y) == 0)
        return true;
    return false;
}
double Dot(Vector A, Vector B){// 数量积
    return A.x*B.x + A.y*B.y;
}
double Cross(Vector A, Vector B){// 叉积
    return A.x*B.y-A.y*B.x;
}
double Length(Vector A){// 向量长度 |a|
    return sqrt(Dot(A, A));
}
double Angle(Vector A, Vector B){// 返回值为弧度制下的夹角
    return acos(Dot(A, B)/Length(A)/Length(B));
}
double Area2(Point A, Point B, Point C){// 计算两向量构成的平行四边形有向面积
    return Cross(B-A, C-A);
}
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));
}
Vector Normal(Vector A){//计算向量A逆时针旋转90°的单位法向量
    double L = Length(A);
    return Vector(-A.y/L, A.x/L);
}
bool ToLeftTest(Point a, Point b, Point c){// 判断向量bc是不是在向量ab的逆时针转向,凸包构造时将会频繁用到此公式
    return Cross(b - a, c - b) > 0;
}

直线

计算机中一般用点向式方程表示直线,即用直线上的一个点 P 0 P_0 P0 和方向向量 v v v 表示
P = P 0 + v t P=P_0+vt P=P0+vt
t t t 为参数

  • 可以表示所有直线
  • 可以通过限制参数来表示线段和射线

判断点在直线上:直线上任取两点,与待测点构成向量,判断两向量叉积是否为 0 0 0,为 0 0 0 则点在直线上
板子

struct Line{//直线定义
    Point v, p;
    Line(Point v, Point p) : v(v), p(p) {}
    Point point(double t){//返回点P = v + (p - v)*t
        return v + (p - v)*t;
    }
};

//计算两直线交点
//调用前需保证 Cross(v, w) != 0,即两直线相交 
Point GetLineIntersection(Point P, Vector v, Point Q, Vector w){
    Vector u = P-Q;
    double t = Cross(w, u)/Cross(v, w);
    return P+v*t;
}

//点P到直线AB距离公式
double DistanceToLine(Point P, Point A, Point B){
    Vector v1 = B-A, v2 = P-A;
    return fabs(Cross(v1, v2)/Length(v1));
}//不去绝对值,得到的是有向距离

//点P到线段AB距离公式
double DistanceToSegment(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) == -1)
        return Length(v2);
    if(dcmp(Dot(v1, v3), 0) == 1)
        return Length(v3);
    return DistanceToLine(P, A, B);
}

//点P在直线AB上的投影点
Point GetLineProjection(Point P, Point A, Point B){
    Vector v = B-A;
    return A+v*(Dot(v, P-A)/Dot(v, v));
}

//判断p点是否在线段a1a2上
bool OnSegment(Point p, Point a1, Point a2){
    return dcmp(Cross(a1-p, a2-p)) == 0 && dcmp(Dot(a1-p, a2-p)) < 0;
}

//判断两线段是否相交
bool SegmentProperIntersection(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, a1-b1), c4 = Cross(b2-b1, a2-b1);
    //if判断控制是否允许线段在端点处相交,根据需要添加
    if(!sgn(c1) || !sgn(c2) || !sgn(c3) || !sgn(c4)){
        bool f1 = OnSegment(b1, a1, a2);
        bool f2 = OnSegment(b2, a1, a2);
        bool f3 = OnSegment(a1, b1, b2);
        bool f4 = OnSegment(a2, b1, b2);
        bool f = (f1|f2|f3|f4);
        return f;
    }
    return (sgn(c1)*sgn(c2) < 0 && sgn(c3)*sgn(c4) < 0);
}

多边形

三角形

求面积

  • 两边叉积除以 2 2 2 取绝对值
  • 二维平面三角形面积,利用叉积: S = ∣ x 1 ∗ y 2 − x 2 ∗ y 1 ∣ 2 S=\frac{|x_1*y_2-x_2*y_1|}{2} S=2x1y2x2y1
  • 已知三点求面积,只需要写出俩个向量坐标,再用叉积求就好了
  • 海伦公式 S = p ( p − a ) ( p − b ) ( p − c ) , p = a + b + c 2 S=\sqrt{p(p-a)(p-b)(p-c)},p=\frac{a+b+c}{2} S=p(pa)(pb)(pc) ,p=2a+b+c
  • S = a b s i n C 2 S=\frac{absinC}{2} S=2absinC
    三角形四心
  • 外心:三边中垂线交点,到三角形三个顶点距离相同
  • 内心:角平分线的交点,到三角形三边的距离相同
  • 垂心:三条高线的交点
  • 重心:三条中线的交点,到三角形三顶点距离的平方和最小的点,三角形内到三边距离之积最大的点

凸多变形

通常按照逆时针顺序存储所有顶点

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

极角排序

极角排序
给定极轴,在平面上分布着若干点,将这些点相对于极轴的极角大小排序,就叫做极角排序。注意如果极角相同,那么需要按 x x x 升序处理

利用atan2(y,x) 排序

常用函数 a t a n 2 ( y , x ) atan2(y,x) atan2(y,x) 进行极角排序,其中 x , y x,y x,y 分别点的坐标,计算反正切值,就是计算从原点 ( 0 , 0 ) (0,0) (0,0) 为起点的向量 ( x , y ) (x,y) (x,y) x x x 轴正向的夹角的弧度值
值域范围: ( − π , π ] (-\pi,\pi] (π,π]

bool cmp(Point a, Point b) {
    if(dcmp(atan2(a.y, a.x) - atan2(b.y, b.x)) == 0) //dcmp为判断浮点数是否为0的函数
        return a.x < b.x;
    return atan2(a.y, a.x) < atan2(b.y, b.x);
}
利用向量叉乘排序

^ 是重载的叉积运算符

bool cmp(Point a, Point b) {
    if((a ^ b) == 0) return a.x < b.x;
    return (a ^ b) > 0;
}

如果需要自定义极点,那么就传入极点

Point o;  //o为极点
bool cmp(Point a, Point b) {
    Point p = a - o, q = b - o;
    if((p ^ q) == 0) return p.x < q.x;
    return (p ^ q) > 0;
}
例题

C. Nearest vectors
题意:
给你 n n n 个向量,起点都是原点,然后问你角度差最小的两个向量是哪两个
思路:
极角排序之后,再扫一遍就好了
但是这道题卡极角排序的精度损失了,把所有的 d o u b l e double double 改成 l o n g long long d o u b l e double double 就过了
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 1e9 + 7;
const double pi = acos(-1);
ll n, m;
struct node{
	int x, y, id;
	ld deg;
	node(int x = 0, int y = 0, int id = 0) : x(x), y(y), id(id){
		deg = atan2(y, x);
		if(deg < 0) deg += 2 * pi;// 把角度范围置成 [0,2pi)
	}
	bool operator<(const node &B)const{
		return deg < B.deg;
	}
}a[maxn];
void work()
{
	cin >> n;
	for(int i = 1; i <= n; ++i){
		int x, y;cin >> x >> y;
		a[i] = node(x, y, i);
	}
	sort(a + 1, a + 1 + n);
	int px = -1, py = -1;
	ld Min = 1e18;
	a[n + 1] = a[1];// 这个也枚举一下
	for(int i = 1; i <= n; ++i)
	{
		ld x = a[i].deg, y = a[i + 1].deg;
		ld t = min(fabs(x - y), 2 * pi - fabs(x - y));
		if(Min > t){
			Min = t;
			px = a[i].id;
			py = a[i + 1].id;
		}
	}
	cout << px << " " << py << endl;
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

Convex—hdu3629
题意:
给出 n n n 个点,求凸四边形的个数,保证不存在三点共线。
思路:

例题1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值