前置知识
向量的叉乘: 设
a
⃗
=
(
x
a
,
y
a
,
z
a
)
,
b
⃗
=
(
x
b
,
y
b
,
z
b
)
\vec a=(x_a,y_a,z_a), \vec b=(x_b, y_b,z_b)
a=(xa,ya,za),b=(xb,yb,zb), 令
a
⃗
\vec a
a和
b
⃗
\vec b
b的叉乘为
c
⃗
\vec c
c, 有:
c
⃗
=
∣
i
j
k
x
a
y
a
z
a
x
b
y
b
z
b
∣
=
(
y
a
z
b
−
z
a
y
b
,
z
a
x
b
−
x
a
z
b
,
x
a
y
b
−
y
a
x
b
)
\vec c=\begin{vmatrix} i & j & k\\ x_a & y_a & z_a\\ x_b & y_b & z_b \end{vmatrix}=(y_az_b-z_ay_b,z_ax_b-x_az_b,x_ay_b-y_ax_b)
c=
ixaxbjyaybkzazb
=(yazb−zayb,zaxb−xazb,xayb−yaxb)
几何意义:
∣
c
⃗
∣
=
∣
a
⃗
×
b
⃗
∣
=
∣
a
⃗
∣
∣
b
⃗
∣
s
i
n
θ
|\vec c|=|\vec a×\vec b|=|\vec a| |\vec b|sin\theta
∣c∣=∣a×b∣=∣a∣∣b∣sinθ (
θ
\theta
θ为
a
⃗
\vec a
a,
b
⃗
\vec b
b向量之间的夹角), 即
∣
c
⃗
∣
|\vec c|
∣c∣ 等于
a
⃗
\vec a
a,
b
⃗
\vec b
b向量构成的平行四边形的面积.
另外
c
⃗
\vec c
c的方向可以用右手螺旋定则判定: 先将
a
⃗
\vec a
a和
b
⃗
\vec b
b移动到同一起点, 右手四指从
a
⃗
\vec a
a方向朝掌心方向旋转到
b
⃗
\vec b
b方向, 则拇指所指方向, 即为结果向量的方向.
PIP 问题
理解了上述的叉乘的知识, 我们就可以解决PIP这个经典问题的一个子集: 判断一个点是否在一个凸多边形内部. 判断算法的一个伪代码如下:
设 p 0 , ⋯ , p n − 1 p_0,\cdots,p_{n-1} p0,⋯,pn−1为凸多边形边界上的点按逆时针方向遍历形成的一个排列, x x x为待判断的点
for i i i in { 0 , ⋯ , n − 1 } \{0,\cdots,n-1\} {0,⋯,n−1}
\;\;\;\; 若 ( p i p ( i + 1 ) % n → × p ( i + 1 ) % n x → ) ⋅ ( 0 , 0 , 1 ) < 0 (\overrightarrow{p_ip_{(i+1)\%n}} \times \overrightarrow{p_{(i+1)\%n}x})\cdot (0,0,1)<0 (pip(i+1)%n×p(i+1)%nx)⋅(0,0,1)<0, 则 x x x不在多边形内部
x x x在多边形内部(包括边界上)
求凸包
上面提到的伪代码需要凸多边形边界上的点的一个逆时针的排列, 但如过这个排列没有给出, 这时候就可以用Andrew凸包算法来求这样的一个排列, Andrew算法大致思想如下:
首先把所有点以横坐标为第一关键字,纵坐标为第二关键字排序。显然排序后最小的元素和最大的元素一定在凸包上。而且因为是凸多边形,我们如果从一个点出发逆时针走,轨迹总是「左拐」的,一旦出现右拐,就说明这一段不在凸包上。因此我们可以用一个单调栈来维护上下凸壳。
下面贴一个Andrew算法的模板
inline pair<int, int> vec_ab(pair<int, int> a, pair<int, int> b) {
return {b.first - a.first, b.second - a.second};
}
inline int cross_prod(pair<int, int> a, pair<int, int> b) {
return a.first * b.second - a.second * b.first;
}
const int maxn = 2e5 + 5;
pair<int, int> p[maxn], h[maxn];
int stk[maxn];
int used[maxn];
int main() {
// stk[] 是整型,存的是下标
int n;
int tp=0 // 初始化栈
//读入p
std::sort(p + 1, p + n + 1); // 对点进行排序
stk[++tp] = 1;
// 栈内添加第一个元素,且不更新 used,使得 1 在最后封闭凸包时也对单调栈更新
for (int i = 2; i <= n; ++i) {
while (tp >= 2 && cross_prod(vec_ab(p[stk[tp - 1]], p[stk[tp]]), vec_ab(p[stk[tp]], p[i])) < 0)//凸包边上的点不算在结果数组内(否则用<0)
used[stk[tp--]] = 0;
used[i] = 1; // used 表示在凸壳上
stk[++tp] = i;
}
int tmp = tp; // tmp 表示下凸壳大小
for (int i = n - 1; i > 0; --i)
if (!used[i]) {
// ↓求上凸壳时不影响下凸壳
while (tp > tmp && cross_prod(vec_ab(p[stk[tp - 1]], p[stk[tp]]), vec_ab(p[stk[tp]], p[i])) < 0)//凸包边上的点不算在结果数组内(否则用<0)
used[stk[tp--]] = 0;
used[i] = 1;
stk[++tp] = i;
}
for (int i = 1; i < tp; ++i) // 复制到新数组中去
h[i] = p[stk[i]];
int ans = tp - 1; // ans为凸包边上节点数
参考:
https://oi-wiki.org/geometry/convex-hull/
https://zhuanlan.zhihu.com/p/385131501