Andrew算法求凸包模板

前置知识

向量的叉乘: 设 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 =(yazbzayb,zaxbxazb,xaybyaxb)
几何意义: ∣ 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,,pn1为凸多边形边界上的点按逆时针方向遍历形成的一个排列, x x x为待判断的点
for i i i in { 0 , ⋯   , n − 1 } \{0,\cdots,n-1\} {0,,n1}
         \;\;\;\; ( 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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值