计算几何基础
蒟蒻的计算几何几乎不会,这里记录一些计算几何的常见概念,以作备忘:
向量
可以用一个二维坐标 \((x, y)\) 表示。比如点 \(A = (x, y)\),则 \(\overrightarrow{OA} = (x, y)\)
\(A(x_{1}, y_{1}), B(x_{2}, y_{2})\) \(\rightarrow\) \(\overrightarrow{AB} = (x_{2} - x_{1}, y_{2} - y_{1})\)
运算:\(\overrightarrow{AB} = \overrightarrow{OB} - \overrightarrow{OA}\)
二维向量运算可以看作是 \(x,y\) 两个方向上的加减运算复合,三维同理。
代码:
// 注意,Point类型既可以表示二维平面中的一个点,也可以表示一个向量
struct Point{
double x, y;
Point(double x, double y): x(x), y(y){}
};
// 向量加法
Point operator + (Point a, Point b){
return Point(a.x + b.x, a.y + b.y);
}
// 向量减法
Point operator - (Point a, Point b){
return Point(a.x - b.x, a.y - b.y);
}
// 数乘
Point operator * (Point a, double t){
return Point(a.x * t, a.y * t);
}
点积
形式:\(\overrightarrow{a} \cdot \overrightarrow{b} = x_{1}x_{2} + y_{1}y_{2} = |a||b| \cos\theta\)
几何意义:\(\overrightarrow{b}\) 在 \(\overrightarrow{a}\) 上的投影 与 \(\overrightarrow{a}\) 的长度 的乘积
代码:
// 求点积
double dot(Point a, Point b){
return a.x * b.x + a.y * b.y;
}
// 求模长
double len(Point a){ // 注意这里a代表一个向量
return sqrt(a.x * a.x + a.y * a.y);
}
// 求夹角
double angle(Point a, Point b){
return acos(dot(a, b) / len(a) / len(b));
}
应用:
- 判断两向量垂直:\(\overrightarrow{a} \cdot \overrightarrow{b} = 0\)
- 判断两向量平行(共线):\(\overrightarrow{a} \cdot \overrightarrow{b} = |\overrightarrow{a}||\overrightarrow{b}|\)
- 求两向量的夹角:\(\cos \theta = \frac{\overrightarrow{a} \cdot \overrightarrow{b}}{|\overrightarrow{a}||\overrightarrow{b}|}\)
叉积
形式:\(\overrightarrow{a} \times \overrightarrow{b} = x_{1}y_{2} - x_{2}y_{1} = |a||b| \sin\theta\)
几何意义:\(\overrightarrow{a}\) 与 \(\overrightarrow{b}\) 张成的平行四边形的有向面积。\(\overrightarrow{b}\) 在 \(\overrightarrow{a}\) 的顺时针方向(右侧)为负;\(\overrightarrow{b}\) 在 \(\overrightarrow{a}\) 的逆时针方向(左侧)为正。
注意,这里的左,右侧是相对于朝着向量方向而言。
代码:
// 求叉积
double det(Point a, Point b){
return a.x * b.y - b.x * a.y;
}
// 向量ab 与 向量ac 的叉积
double cross(Point a, Point b, Point c){
return det(b - a, c - a);
}
// 判断点c在直线ab的哪一侧(1为左侧,-1为右侧,0为三点共线)
int sgn(double x){
if(x == 0) return 0;
return (x > 0 ? 1 : -1);
}
int direction(Point a, Point b, Point c){
return sgn(cross(a, b, c));
}
// 判断直线ab与线段cd是否有交点:
bool iscross(Point a, Point b, Point c, Point d){
return cross(a, b, c) * cross(a, b, d) <= 0;
}
// 判断线段ab与线段cd是否有交点
bool iscross(Point a, Point b, Point c, Point d){
return cross(a, b, c) * cross(a, b, d) <= 0
&& cross(c, d, a) * cross(c, d, b) <= 0;
}
// 得到直线au与直线bv的交点坐标(共线时分母为0,需特判)
Point getcrossnode(Point a, Point u, Point b, Point v){
double t = det((a - b), v) / det(v, u); // 比例
return a + u * t;
}
应用:
- 判定点线之间的位置关系
- \((\overrightarrow{b} - \overrightarrow{a}) \times (\overrightarrow{c} - \overrightarrow{a}) > 0\) \(\rightarrow\) 点 \(c\) 在向量 \(\overrightarrow{ab}\) 的左侧
- \((\overrightarrow{b} - \overrightarrow{a}) \times (\overrightarrow{c} - \overrightarrow{a}) < 0\) \(\rightarrow\) 点 \(c\) 在向量 \(\overrightarrow{ab}\) 的右侧
- \((\overrightarrow{b} - \overrightarrow{a}) \times (\overrightarrow{c} - \overrightarrow{a}) = 0\) \(\rightarrow\) 三点共线
- 判定线线之间的位置关系
- 判断线段 \(ab\) 与 线段 \(cd\) 是否有交点:
(1) \(cross(a, b, c) * cross(a, b, d) > 0\) 或 \(cross(c, d, a) * cross(c, d, b) > 0\) \(\rightarrow\) 无交点
(2)\(cross(a, b, c) * cross(a, b, d) <= 0\) 并且 \(cross(c, d, a) * cross(c, d, b) <= 0\) \(\rightarrow\) 有交点 - 求两个线段的交点 \(\rightarrow\) 见代码部分即可。证明见下图:
交点 \(O\) 可以利用点 \(a\) 在 \(\overrightarrow{au}\) 方向上的偏移得出,\(|au|\) 已知,故只要知道 \(\frac{|oa|}{|ua|}\) 即可。发现它们的关系恰为两个平行四边形的高之比,而底相同,故又等于两个平行四边形的面积之比,而平行四边形的面积可以利用叉积快速得出。
注:上述做法只是在求多边形面积的大小。由叉积的正负性可知,其实多边形的面积也有正负性之分。而 多边形面积的正负性 可以 区分多边形中每条边两侧分别是多边形的外部/内部(一个性质:若将多边形中的所有边按照顺时针或逆时针标注方向,则它们的 左/右侧 与 多边形外部/内部 是一一对应的)。
(待 \(upd...\))