插入均摊 O(log n) 的动态凸包,极角排序
建凸包,除了 Andrew 算法还有 Graham 扫描法。然而有人认为“极角排序”丢精度、效率低,这里我给一个不需要 atan2 的极角排序。
怎么“极角排序”
为了方便,我们定义从原点沿
例如上图
一个朴素的想法:判断两个向量
- 如果
a, b 在y 轴右侧,a 的角度比b 的角度小且当a 的斜率小于b 的斜率。 - 如果
a, b 在y 轴左侧或其中一个在y 轴上另一个在y 轴左侧,将它们绕原点旋转180^{\circ} 二者角度关系不变,变成了第一种情况。 - 如果
a, b 在y 轴两侧,哪个在右侧哪个角度小。 - 当
a, b 在y 轴上时,谁朝下谁角度小。二者都朝下,角度相同。 - 当
a 在y 轴上,b 在y 轴右侧(从情况 2 转换来的),如果a 朝下那么a 角度小,如果a 朝上a 角度大。b 在y 轴上时类似。
原点不会出现在凸包的边缘上,不需要考虑原点的角度。
bool operator<(vec a, vec b) {
if (a.x == 0 && b.x == 0) {
if (signbit(a.y) == signbit(b.y)) { // 4
return false;
}
return a.y < 0;
}
if (a.x <= 0 && b.x <= 0) { // 2
return -a < -b;
}
if ((a.x == 0 && a.y < 0) || (b.x == 0 && b.y > 0)) { // 5
return true;
}
if ((a.x == 0 && a.y > 0) || (b.x == 0 && b.y < 0)) { // 5
return false;
}
if (signbit(a.x) != signbit(b.x)) { // 3
return a.x > b.x;
}
return (long long) a.y * b.x < (long long) b.y * a.x; // 1
}
剩下的部分
我们把给我们所有的点的坐标都乘上 3 。由一开始的三个点,计算出这三个点围成的三角形的重心(因为坐标都乘上 3 所以重心的坐标必为整数),并将所有点一起平移,使得重心落在原点上。这样处理方便我们比较角度。
用平衡树维护凸包边缘上的点(向量),按极角排序。
考虑怎么做询问。记给我们的向量为
// 这里实现的是 "b" 是不是在凸包外。hull 是 std::set 。prev 不是 std::prev ,是我自己写的 prev 。
// prev(hull.begin()) == std::prev(hull.end()) 。
// 同理下面 insert 中的 next 也不是 std::next ,是我自己写的 next 。
bool outHull(vec b) {
auto it = hull.lower_bound(b);
if (it == hull.end()) {
it = hull.begin();
}
auto c = *it, a = *prev(it);
return cross({a, c}, {a, b}) < 0;
}
考虑怎么做插入。首先判断给我们的点
void insert(vec v) {
hull.erase(v); // 方向相同的向量会被判为相等
auto it = hull.insert(v).first;
while (cross({*it, *next(it)}, {*next(it), *next(it, 2)}) <= 0) {
hull.erase(*next(it));
}
while (cross({*it, *prev(it)}, {*prev(it), *prev(it, 2)}) >= 0) {
hull.erase(*prev(it));
}
}
分析下时间复杂度,平衡树选用红黑树,查找
题外话
注意到插入的时间复杂度是均摊
不难想出本题还有一个询问
有问题可以在评论区中问我。如果发现了我的题解有错误麻烦各位指出,谢谢。