在ACM计算几何中,我们常常重复用到许多方法,例如求点距,求直线方程,等等。不妨利用模板总结之。这些模板为我从网上学习后加入自己的理解糅合得来qwq,看了很多的博客,查了很多书籍,其中印象最深的是林夕林夕关于计算几何模板的总结,大家也可以去参考他的应该比我的更详细qwq
(不过大佬最近好像出没比较少了)
所以我这个主要还是留着自己参考吧,能帮到他人再好不过了
为了不产生歧义,方便理解,本模板使用笛卡尔坐标系,封装的多是数学中常见的函数。
前置知识:高中数学,向量外积相关
模板仅供参考学习,不保证任何情况下不会因为误差WA掉或是RE掉
基础声明
首先,打acm的时候double这个数据类型就比较恶心(容易浮动导致误差),而计算几何中各种计算又几乎没法单独用整型解决。因此我们需要一个小常数来判断误差。
例如,计算啥啥面积的时候,我们需求误差在10的-6次方以内,那么就可以定义这个常数为10的-6次方:
const double eps = 1e-6;
然后,我们的目标是,当误差小于1e-6时,误差将被忽略不计。也就是说,两个数比较时,差距在1e-6以内,我们就可以认为这两个数相等。
一般的计算中,本来两个相等的数字,因为我们计算路径的不同,浮点数就可能会发生不同的浮动,导致 = = == ==运算符检查本来应该相等的两个浮点数时返回了0。这时我们就可以用误差来避免了。
以下为重载大小判断的函数:
int cmp(const double& a, const double& b)
{
if(fabs(a - b) < eps) return 0; // 若误差范围内相等,返回0
else if(a > b) return 1; // 若a > b,返回1
return -1; // 若a < b,返回-1
}
另外还有计算几何中常用的常量:
const double pi = 3.1415927536;
const double INF = 1e100;
浮点数的表数范围巨大,可以表示到10的200多次方,但是实际上几十次方浮动误差就大到难以接受了……因此,我们可以用一个100次方表示无穷大,反正它够大就对了,但是却不能直接拿来做计算(总之就是误差非常大)。
另外,我们也常用C++内置的反三角函数acos()
来求角度。但是这个函数有个问题,我们都知道反余弦函数的定义域是
[
−
1
,
1
]
[-1,1]
[−1,1],然而由于浮点数的浮动偏移,我们得到的1很可能实际上是1.00001,1.0000001这种数,这些数据不在反余弦函数的定义域内了,那么把它们作为参数传进去就没法计算,就会导致Runtime Error,就没法AC!QAQ!所以我们可以把acos()
函数二次包装一下,打消这个顾虑。
double acs(double x)
{
if(x > 1) x = 1;
else if(x < -1) x = -1;
return acos(x);
}
顺便把反正弦函数也包装一下
double asn(double x)
{
if(x > 1) x = 1;
else if(x < -1) x = -1;
return asin(x);
}
当然啦,上面这个函数的调用还得保证理论上传入的x确实在 [ − 1 , 1 ] [-1,1] [−1,1]范围内。
另外还有一个关于double注意事项,即其应该以%lf
格式读入,而以%f
格式输出。寒假曾因为这个在好几个图论题上wa了,自己也不知道为啥,后来也有很多巨巨强调……
好,这些就是基本需要注意的地方,下面我们来建立笛卡尔的王国吧~
点
直接定义:
struct point
{
double x, y;
point(double xx = 0, double yy = 0){x = xx, y = yy;}
};
即可。
然后点与点之间有一系列的计算函数,下面列出我用过的几个:
double dis(const point& a, const point& b) // 计算两点距离
{
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
double slope(const point& a, const point& b) // 求两点斜率
{
if(cmp(a.x, b.x) == 0) return INF;
return (a.y - b.y) / (a.x - b.x);
}
double gradient(const point& a, const point& b) // 计算倾斜角
{
if(cmp(a.y, b.y) == 0) return 0;
if(cmp(a.x, b.x) == 0) return pi / 2.0;
return atan(slope(a, b));
}
只有点的就到这里,下面的向量是应用更多的~
向量
由于空间中的向量也可以用坐标表示,因此我们不妨:
typedef point Vector;
V大写是因为关键字vector已经被STL用去了。(C无视我)
在计算几何领域,使用向量不仅操作比传统解析几何方法更简洁易写,而且产生的浮点误差也更小。因此,向量是计算几何的重头戏。
先看看高中学过的一些东西:
向量点积,重载一下:
double operator * (const Vector& a, const Vector& b)
{
return a.x * b.x + a.y * b.y;
}
接着是几大运算,向量加减,数乘等等等等
Vector operator + (const Vector& a, const Vector& b) // 向量加法
{
return Vector(a.x + b.x, a.y + b.y);
}
Vector operator - (const Vector& a, const Vector& b) // 向量减法
{
return Vector(a.x - b.x, a.y - b.y);
}
Vector operator * (const Vector& a, const double& b) // 向量数乘
{
return Vector(a.x * b, a.y * b);
}
double length(const Vector& x) // 向量模长
{
return sqrt(x * x);
}
Vector angle(const Vector& a, const Vector& b) // 向量夹角
{
return acs((a * b) / length(a) / length(b));
}
向量叉乘,返回结果向量的有向长度。用^
运算符重载之。
注意叉乘的绝对值也就是两向量围成的平行四边形的面积。
double operator ^ (const Vector& a, const Vector& b)
{
return a.x * b.y - a.y * b.x;
}
对向量进行操作。向量逆时针旋转:
Vector Rotate(const Vector& a, double rad)
{
return Vector(A.x * cos(rad) - A.y * sin(rad), A.x * sin(rad) + A.y * cos(rad));
}
接下来,点的排序。在诸如求凸包的一类问题中,我们需要为点进行排序方便操作,这里以Andrew凸包算法中的Left then Low排序为例给一个排序函数:
bool leftThenLow(const point& a, const point& b)
{
if(cmp(a.x, b.x) == 0) return a.y < b.y;
return a.x < b.x;
}
以及Graham Scan中必不可少的极角排序:
point p; // 基准点
bool angleSort(const point& a, const point& b)
{
return (angle(a - p, Vecotr(1, 0)) < angle(b - p, Vector(1, 0)));
}
直线
计算几何体系的直线通常使用向量表示法(类似参数方程)而不是一般的 y = k x + b y=kx+b y=kx+b的形式。一般我们这样表示直线: y = v t + p y=vt+p y=vt+p. 其中 v v v是一个向量,而 p p p是某个点。其几何意义为从一定点 p p p出发,经过 t t t倍 v v v所能到达的点的集合。这样的表示方法是因为可以表示无斜率的直线和斜率为0的直线,也能表示线段。有了这个表示法,我们再进行一系列函数封装。
struct line
{
double v, p;
line(double vv = 0, double pp = 0){v = vv, p = pp;}
};
点线距离:
double dis(const line& l, const point& x)
{
Vector v = l.p - x;
return fabs(v ^ l.v) / length(l.v);
}
点是否在线上:
bool pointOnLine(const point& x, const line& l)
{
return cmp((Vector(l.p - x) ^ l.v), 0);
}
点在直线上的投影:
point prj(const point& x, const line& l)
{
return l.p + l.v * (l.v * (x - l.p) / (l.v * l.v));
}
圆
使用标准方法表示:圆心和半径。
struct circle
{
point p;
double r;
circle(double x, double y, double rr){p.x = x, p.y = y, r = rr;}
};