前置知识
向量的叉乘: 设 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 // 初始化栈//读入pstd::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