本文基于fxj巨佬的计算几何全家桶,并基于原文进行了自己的一些整理了经验补充,阅读本文前请前往支持巨佬fxj。
计算几何的使用有一些基本原则,它们能帮助你快速地找到代码中出现的问题并方向明确地解决它们。
由于计算几何常用double类型计算,因此丢精度是必然会出现的。这个问题主要体现在比较运算中。
举个例子,在计算几何中算出一个三角形的面积是2.999,但是我们知道这肯定是存在误差的,因此它实际上应当不是2.99,假设在没有误差时它的面积应是3,那么我们要把这个三角面积比较时,就要忽略这一点小误差。
解决这个问题,就要引入贯穿了整个计算几何的核心内容:存在误差时的比较运算
e p s eps eps是一个极小量,通常在 [ 1 0 − 12 , 1 0 − 8 ] [10^{-12},10^{-8}] [10−12,10−8]的范围内,表示精度要求。
a = b a=b a=b → → → a b s ( a − b ) abs(a−b) abs(a−b) < < < e p s eps eps
a < b a < b a<b → → → a < b a < b a<b − − − e p s e p s eps
a > b a > b a>b → → → a > b a>b a>b + + + e p s eps eps
以上比较式的很容易证明:
由于浮点数计算产生的精度误差是普遍存在于计算几何中,因此通过计算集合得出的数进行的比较运算中,必须用上文的格式写出。
最容易出现的一个错误就是在将某数 a a a与 0 0 0比较时将式子写成 a > 0 a>0 a>0而非 a > 0 + e p s a>0+eps a>0+eps即 a > e p s a>eps a>eps
范神在部分模板中忽略了这一点,现已更正
在计算几何中,点和向量往往是等价的,且经常出现一个变量被同时当作点和向量使用的现象,因此一起表示。
struct V
{
double x,y;
V():x(0),y(0){};
V(double a,double b):x(a),y(b){};
}dot[maxn];
inline void input(V &a){a.x=read();a.y=read();}
void print(const V &a,int op=1){printf("%.10lf %.10lf",a.x,a.y);putchar(op?10:32);}
//op=endl or space
基本的加、减、乘、除、点积、叉积、模长、中点、垂直向量、向量单位化、三角形面积。
拓展:
高维点积:定义不变,但在投影方面几何意义只对二维和三维空间有效。
高维叉积:叉积在k维下的定义是三行k列的行列式,第一行为各维度的单位向量,二三行为两个点各自在对应维度的坐标。
注意:
(1)严格意义上,向量叉积是一个(更高维,二维向量叉积为三维)的向量,模长为 x 1 y 2 − x 2 y 1 x_1y_2-x_2y_1 x1y2−x2y1,方向垂直与纸面,遵循右手定则,这也是叉积可以表示负面积的原因。
(被叉积表示面积的平行四边形是叉积在二维平面内的投影)。
(2)以下模板中的垂直向量并没有考虑方向的问题。
struct V
{
double x,y;
V():x(0),y(0){};
V(double a,double b):x(a),y(b){};
}//点和向量的存储
inline void input(V &a){a.x=read();a.y=read();}
void print(const V &a,int op=1){printf("%.10lf %.10lf",a.x,a.y);putchar(op?10:32);}
//op=endl or space
inline V operator + (const V &a,const V &b){return (V){a.x+b.x,a.y+b.y};}
inline V operator - (const V &a,const V &b){return (V){a.x-b.x,a.y-b.y};}
inline V operator * (const V &a,const double &x){return (V){a.x*x,b.y*x};}
inline V operator * (const double &x,const V &a){return (V){a.x*x,b.y*x};}
inline V operator / (const V &a,const double &x){return (V){a.x/x,b.y/x};}
inline bool operator == (const V &a,const V &b){return abs(a.x-b.x)<eps && abs(a.y-b.y)<eps;}
inline bool operator != (const V &a,const V &b){return !(a==b);}
inline double operator * (const V &a,const V &b){return a.x*b.x+a.y*b.y;}
inline double operator ^ (const V &a,const V &b){return a.x*b.y-b.x*a.y;}
inline double len (const V &a,const V &b){return sqrt(a.x*b.x+a.y*b.y);}
inline V mid(const V &a,const V &b){return (V){(a.x+b.x)/2,(a.y+b.y)/2};}中点坐标公式求中点
inline V cui(const V &a){return (V){a.y,-a.x};}//求垂直向量,使用k1*k2=-1的性质
inline V danwei(const V &a){return a/len(a);}//求单位向量
inline double tri_S(const V &a,const V &b,const V &c){return abs((b-a)*(c-a))/2;}//求向量或三点所成三角形面积
inline bool operator < (const V &a,const V &b){return a.x<b.x || (abs(a.x-b.x)<eps && a.y<b.y-eps);}
//这个向量的大小比较是将向量按照x为第一关键字,y为第二关键字,升序排列。
//这个算符会在求凸包中被大量使用,平时倒是没什么用
用向量点积易得。
这个角的取值范围是 [ 0 , π ] [0,π] [0,π].
inline double angle(const V &a,const V &b){return acos( a*b / len(a) / len(b));}
inline bool dun(const V &a,const V &b,const V &c){return ((b-a)*(c-a))<-eps;}//angle:BAC
inline bool rui(const V &a,const V &b,const V &c){return ((b-a)*(c-a))>eps;}
inline bool zhi(const V &a,const V &b,const V &c){return abs(((b-a)*(c-a)))<eps;}
设原向量为 ( x , y ) (x,y) (x,y),旋转角度为 θ θ θ,(注意:θ的正负判断遵循右手定则,即逆时针旋转的角为正角),
则旋转后的得到向量为 ( x c o s θ − y s i n θ , x s i n θ + y c o s θ ) (xcosθ-ysinθ,xsinθ+ycosθ) (xcosθ−ysinθ,xsinθ+ycosθ)。
证明:用复数乘法,在复平面内 ( x , y i ) ⋅ ( c o s θ , s i n θ i ) (x,yi)·(cosθ,sinθi) (x,yi)⋅(cosθ,sinθi),结合复平面的性质可得 ( x c o s θ − y s i n θ , i x s i n θ + i y c o s θ ) (xcosθ-ysinθ,ixsinθ+iycosθ) (xcosθ−ysinθ,ixsinθ+iycosθ),结果同上.
inline V rotate(const V &a,const double t)
{
double s=sin(t),c=cos(t);
return (V){a.x*c-a.y*s,a.x*s+a.y*c};
}