競技プログラミング用 C++ ライブラリ
geometry/dynamic_convex_hull.hpp · GitHub で見る
共通ページ
整数座標の点を順不同に追加し、凸包を維持します。凸包の辺上の中間点・内部点・重複点は頂点列に残しません。点の削除には対応しません。
#include "lib/geometry/dynamic_convex_hull.hpp"
#include <cassert>
geometry::DynamicConvexHull<> hull;
hull.insert({0, 0});
hull.insert({4, 0});
hull.insert({0, 3});
assert(!hull.insert({1, 1}));
assert(hull.size() == 3);
assert(hull.doubled_area() == 12);
assert(hull.contains({1, 1}) == geometry::Location::inside);
assert(*hull.max_dot({1, 1}) == 4);
assert(*hull.min_dot({1, 1}) == 0);
auto vertices = hull.vertices(); // (0,0), (4,0), (0,3)
名前空間は geometry です。DynamicConvexHull<T = std::int64_t> に渡す点・方向ベクトルは Point<T> です。T は64bit以下の符号付き整数型とし、すべての座標・方向ベクトルの各成分を \([-10^{18},10^{18}]\) に収めてください。浮動小数点座標は扱いません。面積の2倍と内積の型は __int128_t で、範囲内では丸め誤差なく計算します。
| 操作 | 内容・返り値 | 計算量 |
|---|---|---|
DynamicConvexHull<T>() |
空の凸包を作る | \(O(1)\) |
DynamicConvexHull(points) |
vector<Point<T>> の全点を追加 |
\(O(n\log n)\) |
insert(p) |
点を追加。凸包が変化すれば true、それ以外は false
|
償却 \(O(\log n)\) |
size() / empty()
|
凸包の頂点数 size_t/空か bool
|
\(O(1)\) |
vertices() |
辞書順最小の頂点から反時計回りの vector<Point<T>> を返す |
\(O(h)\) |
contains(p) |
Location::outside・boundary・inside を返す |
\(O(\log h)\) |
support(direction) |
内積を最大にする頂点を optional<Point<T>> で返す。同点は任意 |
\(O(\log h)\) |
max_dot(direction) / min_dot(direction)
|
内積の最大/最小を optional<__int128_t> で返す |
\(O(\log h)\) |
doubled_area() |
面積の2倍を __int128_t で返す |
\(O(1)\) |
clear() |
全点を削除して空に戻す | \(O(h)\) |
swap(other) |
内容を交換 | \(O(1)\) |
空の場合、vertices() は空配列、contains() は outside、support()・max_dot()・min_dot() は nullopt、doubled_area() は0です。全点が一直線上なら頂点列は両端のみになり、線分上の点は boundary です。1点の場合もその点のみが boundary です。ゼロ方向の内積は0で、非空なら support は任意の頂点を返します。
内部点は保持しないため、size() は追加した点数ではありません。取得した頂点列は独立したコピーです。コピーした構造は独立して更新でき、移動元は空になり再利用できます。
clear() 以降の追加回数を \(n\)、現在の凸包の頂点数を \(h\) として、追加は償却 \(O(\log n)\)、包含判定・支持点・内積の問い合わせは \(O(\log h)\)、頂点列の取得・コピーは \(O(h)\) 時間です。面積・頂点数は \(O(1)\) 時間、使用空間は \(O(h)\)、移動・交換は \(O(1)\) 時間です。
#ifndef DYNAMIC_CONVEX_HULL_HPP
#define DYNAMIC_CONVEX_HULL_HPP
#include "point.hpp"
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <limits>
#include <optional>
#include <set>
#include <type_traits>
#include <vector>
namespace geometry {
template<class T = std::int64_t> class DynamicConvexHull {
static_assert(std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) <= 8);
using P = Point<T>;
using W = __int128_t;
struct Direction { W x, y; };
struct Node { P point; mutable P next; mutable bool last; };
struct Compare {
using is_transparent = void;
bool operator()(const Node &a, const Node &b) const { return a.point < b.point; }
bool operator()(const Node &a, P b) const { return a.point < b; }
bool operator()(P a, const Node &b) const { return a < b.point; }
bool operator()(const Node &a, Direction d) const {
return !a.last && (W(a.next.x) - a.point.x) * d.x + (W(a.next.y) - a.point.y) * d.y > 0;
}
bool operator()(Direction d, const Node &a) const { return !(*this)(a, d); }
};
struct Chain {
std::set<Node, Compare> nodes;
W area = 0;
using Iterator = typename std::set<Node, Compare>::iterator;
void erase(Iterator i) {
auto j = std::next(i);
if (j != nodes.end()) area -= cross(i->point, j->point);
if (i != nodes.begin()) {
auto p = std::prev(i);
area -= cross(p->point, i->point);
p->last = j == nodes.end();
if (j != nodes.end()) { p->next = j->point; area += cross(p->point, j->point); }
}
nodes.erase(i);
}
bool insert(P p, bool upper) {
auto next = nodes.lower_bound(P{p.x, std::numeric_limits<T>::lowest()});
if (next != nodes.end() && next->point.x == p.x) {
if (upper ? p.y <= next->point.y : p.y >= next->point.y) return false;
auto old = next++;
erase(old);
}
auto turn = [&](P a, P b, P c) { auto value = cross(a, b, c); return upper ? -value : value; };
if (next != nodes.begin() && next != nodes.end() && turn(std::prev(next)->point, p, next->point) <= 0) return false;
auto i = nodes.insert(next, Node{p, next == nodes.end() ? p : next->point, next == nodes.end()});
if (next != nodes.end()) area += cross(p, next->point);
if (i != nodes.begin()) {
auto prev = std::prev(i);
if (next != nodes.end()) area -= cross(prev->point, next->point);
area += cross(prev->point, p);
prev->next = p, prev->last = false;
}
while (i != nodes.begin()) {
auto prev = std::prev(i);
if (prev == nodes.begin() || turn(std::prev(prev)->point, prev->point, p) > 0) break;
erase(prev);
}
while (std::next(i) != nodes.end()) {
auto j = std::next(i), k = std::next(j);
if (k == nodes.end() || turn(p, j->point, k->point) > 0) break;
erase(j);
}
return true;
}
int side(P p, bool upper) const {
if (nodes.empty() || p.x < nodes.begin()->point.x || p.x > nodes.rbegin()->point.x) return -1;
auto i = nodes.lower_bound(P{p.x, std::numeric_limits<T>::lowest()});
W value;
if (i != nodes.end() && i->point.x == p.x) value = W(p.y) - i->point.y;
else value = cross(std::prev(i)->point, i->point, p);
if (upper) value = -value;
return (value > 0) - (value < 0);
}
};
Chain lower_, upper_;
static void check(P p) {
[[maybe_unused]] constexpr W limit = W(1000000000000000000LL);
assert(-limit <= W(p.x) && W(p.x) <= limit && -limit <= W(p.y) && W(p.y) <= limit);
}
std::optional<P> support_impl(Direction d) const {
if (empty()) return std::nullopt;
const auto &nodes = d.y < 0 ? lower_.nodes : upper_.nodes;
if (!d.y) return d.x > 0 ? nodes.rbegin()->point : nodes.begin()->point;
return nodes.lower_bound(d)->point;
}
public:
using point_type = P;
using value_type = W;
DynamicConvexHull() = default;
explicit DynamicConvexHull(const std::vector<P> &points) { for (auto p : points) insert(p); }
DynamicConvexHull(const DynamicConvexHull &) = default;
DynamicConvexHull(DynamicConvexHull &&other) noexcept { swap(other); }
DynamicConvexHull &operator=(DynamicConvexHull other) noexcept { swap(other); return *this; }
void swap(DynamicConvexHull &other) noexcept {
lower_.nodes.swap(other.lower_.nodes), upper_.nodes.swap(other.upper_.nodes);
std::swap(lower_.area, other.lower_.area), std::swap(upper_.area, other.upper_.area);
}
friend void swap(DynamicConvexHull &a, DynamicConvexHull &b) noexcept { a.swap(b); }
bool empty() const { return lower_.nodes.empty(); }
std::size_t size() const {
if (empty()) return 0;
auto result = lower_.nodes.size() + upper_.nodes.size();
bool first = lower_.nodes.begin()->point == upper_.nodes.begin()->point;
bool last = lower_.nodes.rbegin()->point == upper_.nodes.rbegin()->point;
return result - first - (last && result > 2);
}
void clear() { lower_.nodes.clear(); upper_.nodes.clear(); lower_.area = upper_.area = 0; }
bool insert(P p) { check(p); bool a = lower_.insert(p, false), b = upper_.insert(p, true); return a || b; }
std::vector<P> vertices() const {
std::vector<P> result;
result.reserve(size());
for (const auto &node : lower_.nodes) result.push_back(node.point);
for (auto i = upper_.nodes.rbegin(); i != upper_.nodes.rend(); ++i)
if (result.empty() || result.back() != i->point) result.push_back(i->point);
if (result.size() > 1 && result.front() == result.back()) result.pop_back();
return result;
}
W doubled_area() const {
if (empty()) return 0;
return lower_.area - upper_.area + cross(lower_.nodes.rbegin()->point, upper_.nodes.rbegin()->point)
+ cross(upper_.nodes.begin()->point, lower_.nodes.begin()->point);
}
Location contains(P p) const {
check(p);
if (lower_.side(p, false) < 0 || upper_.side(p, true) < 0) return Location::outside;
if (p.x == lower_.nodes.begin()->point.x || p.x == lower_.nodes.rbegin()->point.x
|| !lower_.side(p, false) || !upper_.side(p, true) || !doubled_area()) return Location::boundary;
return Location::inside;
}
std::optional<P> support(P direction) const { check(direction); return support_impl(Direction{direction.x, direction.y}); }
std::optional<W> max_dot(P direction) const {
auto p = support(direction);
return p ? std::optional<W>(dot(*p, direction)) : std::nullopt;
}
std::optional<W> min_dot(P direction) const {
check(direction);
auto p = support_impl(Direction{-W(direction.x), -W(direction.y)});
return p ? std::optional<W>(dot(*p, direction)) : std::nullopt;
}
};
}
#endif
#line 1 "geometry/dynamic_convex_hull.hpp"
#line 1 "geometry/point.hpp"
#line 1 "components/radix_sort.hpp"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <utility>
#include <vector>
template <int Bits, class T, class Key>
void stable_radix_sort(std::vector<T> &values, Key &&key) {
static_assert(1 <= Bits && Bits <= 16);
constexpr unsigned buckets = 1U << Bits, mask = buckets - 1;
using K = std::remove_cvref_t<decltype(key(std::declval<const T &>()))>;
static_assert(std::is_integral_v<K> && std::is_unsigned_v<K> && sizeof(K) <= sizeof(std::uint64_t));
if (values.size() < 2) return;
if (values.size() < 256) {
std::stable_sort(values.begin(), values.end(), [&](const T &a, const T &b) { return key(a) < key(b); });
return;
}
std::uint64_t first = key(values.front()), previous = first, varying = 0;
bool sorted = true;
for (std::size_t i = 1; i < values.size(); ++i) {
std::uint64_t current = key(values[i]);
varying |= first ^ current;
sorted &= previous <= current;
previous = current;
}
if (sorted) return;
std::vector<T> scratch(values.size());
using Counts = std::conditional_t<(Bits <= 11), std::array<std::size_t, buckets>, std::vector<std::size_t>>;
Counts count = [] {
if constexpr (Bits <= 11) return Counts{};
else return Counts(buckets);
}();
for (int shift = 0; shift < 64; shift += Bits) {
if (!((varying >> shift) & mask)) continue;
std::fill(count.begin(), count.end(), 0);
for (const auto &value : values) ++count[(static_cast<std::uint64_t>(key(value)) >> shift) & mask];
std::size_t sum = 0;
for (auto &x : count) {
auto n = x;
x = sum, sum += n;
}
for (auto &&value : values) scratch[count[(static_cast<std::uint64_t>(key(value)) >> shift) & mask]++] = std::move(value);
values.swap(scratch);
}
}
template <class T, class Key>
void stable_radix_sort(std::vector<T> &values, Key key) {
stable_radix_sort<8>(values, std::move(key));
}
namespace radix_sort_detail {
template <int Bits = 8, class T, class Key>
void sort_by_key(std::vector<T> &values, Key key) {
using K = std::remove_cvref_t<decltype(key(std::declval<const T &>()))>;
if constexpr (std::is_integral_v<K> && !std::is_same_v<K, bool> && sizeof(K) <= 8 && std::is_default_constructible_v<T>) {
using U = std::make_unsigned_t<K>;
constexpr U sign = std::is_signed_v<K> ? U(1) << (sizeof(K) * 8 - 1) : U(0);
stable_radix_sort<Bits>(values, [&](const T &value) { return U(U(key(value)) ^ sign); });
} else std::sort(values.begin(), values.end(), [&](const T &a, const T &b) { return key(a) < key(b); });
}
}
#line 5 "geometry/point.hpp"
#include <cmath>
#line 7 "geometry/point.hpp"
#include <istream>
#include <numbers>
#include <optional>
#include <ostream>
#line 12 "geometry/point.hpp"
namespace geometry {
using Real = long double;
inline constexpr Real epsilon = 1e-12L;
inline constexpr Real pi = std::numbers::pi_v<Real>;
enum class Location { outside = -1, boundary = 0, inside = 1 };
template<class T>
inline constexpr bool integer = std::is_integral_v<T> || std::is_same_v<T, __int128_t> || std::is_same_v<T, __uint128_t>;
template<class T>
using wide_t = std::conditional_t<integer<T>, __int128_t, Real>;
template<class T>
struct Point {
static_assert((std::is_arithmetic_v<T> && std::is_signed_v<T>) || std::is_same_v<T, __int128_t>);
T x{}, y{};
constexpr Point() = default;
constexpr Point(T x, T y) : x(x), y(y) {}
template<class U> explicit constexpr Point(const Point<U>& p) : x(static_cast<T>(p.x)), y(static_cast<T>(p.y)) {}
constexpr bool operator==(const Point&) const = default;
constexpr bool operator<(const Point& p) const { return x < p.x || (x == p.x && y < p.y); }
constexpr Point operator-() const { return {-x, -y}; }
constexpr Point& operator+=(const Point& p) { x += p.x; y += p.y; return *this; }
constexpr Point& operator-=(const Point& p) { x -= p.x; y -= p.y; return *this; }
constexpr Point& operator*=(T c) { x *= c; y *= c; return *this; }
constexpr Point& operator/=(T c) { x /= c; y /= c; return *this; }
friend constexpr Point operator+(Point p, const Point& q) { p += q; return p; }
friend constexpr Point operator-(Point p, const Point& q) { p -= q; return p; }
friend constexpr Point operator*(Point p, T c) { p *= c; return p; }
friend constexpr Point operator*(T c, Point p) { p *= c; return p; }
friend constexpr Point operator/(Point p, T c) { p /= c; return p; }
friend std::istream& operator>>(std::istream& in, Point& p) { return in >> p.x >> p.y; }
friend std::ostream& operator<<(std::ostream& out, const Point& p) { return out << p.x << ' ' << p.y; }
};
using IPoint = Point<std::int64_t>;
using FPoint = Point<Real>;
template<class T>
constexpr int sign(T value, Real eps = epsilon) {
if constexpr (integer<T>) return (value > 0) - (value < 0);
else return (value > eps) - (value < -eps);
}
template<class T>
constexpr wide_t<T> dot(Point<T> a, Point<T> b) {
return wide_t<T>(a.x) * b.x + wide_t<T>(a.y) * b.y;
}
template<class T>
constexpr wide_t<T> cross(Point<T> a, Point<T> b) {
return wide_t<T>(a.x) * b.y - wide_t<T>(a.y) * b.x;
}
template<class T>
constexpr wide_t<T> cross(Point<T> a, Point<T> b, Point<T> c) {
using W = wide_t<T>;
return (W(b.x) - a.x) * (W(c.y) - a.y) - (W(b.y) - a.y) * (W(c.x) - a.x);
}
template<class T>
constexpr wide_t<T> norm2(Point<T> p) { return dot(p, p); }
template<class T>
constexpr wide_t<T> distance_squared(Point<T> a, Point<T> b) {
using W = wide_t<T>;
W x = W(a.x) - b.x, y = W(a.y) - b.y;
return x * x + y * y;
}
template<class T>
constexpr int orientation(Point<T> a, Point<T> b, Point<T> c, Real eps = epsilon) { return sign(cross(a, b, c), eps); }
template<class T>
constexpr int ccw(Point<T> a, Point<T> b, Point<T> c, Real eps = epsilon) {
if (int turn = orientation(a, b, c, eps)) return turn;
using W = wide_t<T>;
W x = W(b.x) - a.x, y = W(b.y) - a.y, u = W(c.x) - a.x, v = W(c.y) - a.y;
if (sign(x * u + y * v, eps) < 0) return 2;
if (sign(u * u + v * v - x * x - y * y, eps) > 0) return -2;
return 0;
}
template<class T>
constexpr FPoint to_real(Point<T> p) { return FPoint(p); }
template<class T>
Real length(Point<T> p) { return std::hypot(Real(p.x), Real(p.y)); }
template<class T>
Real distance(Point<T> a, Point<T> b) {
return std::hypot(Real(wide_t<T>(a.x) - b.x), Real(wide_t<T>(a.y) - b.y));
}
template<class T>
constexpr bool almost_equal(Point<T> a, Point<T> b, Real eps = epsilon) {
if constexpr (integer<T>) return a == b;
else return std::abs(Real(a.x) - b.x) <= eps && std::abs(Real(a.y) - b.y) <= eps;
}
template<class T>
constexpr Point<T> rotate90(Point<T> p) { return {-p.y, p.x}; }
template<class T>
Real argument(Point<T> p) { return std::atan2(Real(p.y), Real(p.x)); }
template<class T>
Real angle(Point<T> a, Point<T> b) { return std::atan2(Real(cross(a, b)), Real(dot(a, b))); }
template<class T>
FPoint rotate(Point<T> p, Real theta) {
Real c = std::cos(theta), s = std::sin(theta);
return {Real(p.x) * c - Real(p.y) * s, Real(p.x) * s + Real(p.y) * c};
}
template<class T>
std::optional<FPoint> unit(Point<T> p) {
Real n = length(p);
if (!n) return std::nullopt;
return to_real(p) / n;
}
template<class T>
struct PolarLess {
bool operator()(Point<T> a, Point<T> b) const {
if (a == Point<T>{} || b == Point<T>{}) return a == Point<T>{} && b != Point<T>{};
auto half = [](Point<T> p) { return p.y < 0 || (p.y == 0 && p.x < 0); };
if (half(a) != half(b)) return half(a) < half(b);
if constexpr (integer<T> && sizeof(T) <= 4) {
auto c = std::int64_t(a.x) * b.y - std::int64_t(a.y) * b.x;
if (c) return c > 0;
auto norm = [](Point<T> p) { return std::uint64_t(std::int64_t(p.x) * p.x) + std::uint64_t(std::int64_t(p.y) * p.y); };
return norm(a) < norm(b);
} else {
if (auto c = cross(a, b); c != 0) return c > 0;
return norm2(a) < norm2(b);
}
}
};
template<class T>
void polar_sort(std::vector<Point<T>> &points) {
if constexpr (integer<T> && sizeof(T) <= 4) {
if (points.size() >= 256) {
struct Entry { std::uint64_t key; Point<T> point; };
std::vector<Entry> order;
order.reserve(points.size());
constexpr std::uint64_t scale = (std::uint64_t(1) << 61) - 1;
for (auto p : points) {
std::int64_t x = p.x, y = p.y;
std::uint64_t a = x < 0 ? -x : x, b = y < 0 ? -y : y;
unsigned quadrant = y < 0 ? (x < 0 ? 2 : 3) : (x < 0 ? 1 : 0);
unsigned octant = quadrant * 2 + ((a < b) ^ (quadrant & 1));
std::uint64_t ratio = a || b ? std::min(scale, std::uint64_t(double(std::min(a, b)) / double(std::max(a, b)) * double(scale))) : 0;
if (octant & 1) ratio = scale - ratio;
order.push_back({(std::uint64_t(octant) << 61) | ratio, p});
}
stable_radix_sort<11>(order, [](const Entry &entry) { return entry.key; });
for (std::size_t first = 0; first < order.size();) {
std::size_t last = first + 1;
while (last < order.size() && order[last].key == order[first].key) ++last;
auto less = [](const Entry &a, const Entry &b) { return PolarLess<T>{}(a.point, b.point); };
if (last - first <= 8 || !std::is_sorted(order.begin() + first, order.begin() + last, less))
std::sort(order.begin() + first, order.begin() + last, less);
first = last;
}
for (std::size_t i = 0; i < points.size(); ++i) points[i] = order[i].point;
return;
}
}
std::sort(points.begin(), points.end(), PolarLess<T>{});
}
}
#line 5 "geometry/dynamic_convex_hull.hpp"
#include <cassert>
#line 8 "geometry/dynamic_convex_hull.hpp"
#include <iterator>
#include <limits>
#line 11 "geometry/dynamic_convex_hull.hpp"
#include <set>
#line 14 "geometry/dynamic_convex_hull.hpp"
namespace geometry {
template<class T = std::int64_t> class DynamicConvexHull {
static_assert(std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) <= 8);
using P = Point<T>;
using W = __int128_t;
struct Direction { W x, y; };
struct Node { P point; mutable P next; mutable bool last; };
struct Compare {
using is_transparent = void;
bool operator()(const Node &a, const Node &b) const { return a.point < b.point; }
bool operator()(const Node &a, P b) const { return a.point < b; }
bool operator()(P a, const Node &b) const { return a < b.point; }
bool operator()(const Node &a, Direction d) const {
return !a.last && (W(a.next.x) - a.point.x) * d.x + (W(a.next.y) - a.point.y) * d.y > 0;
}
bool operator()(Direction d, const Node &a) const { return !(*this)(a, d); }
};
struct Chain {
std::set<Node, Compare> nodes;
W area = 0;
using Iterator = typename std::set<Node, Compare>::iterator;
void erase(Iterator i) {
auto j = std::next(i);
if (j != nodes.end()) area -= cross(i->point, j->point);
if (i != nodes.begin()) {
auto p = std::prev(i);
area -= cross(p->point, i->point);
p->last = j == nodes.end();
if (j != nodes.end()) { p->next = j->point; area += cross(p->point, j->point); }
}
nodes.erase(i);
}
bool insert(P p, bool upper) {
auto next = nodes.lower_bound(P{p.x, std::numeric_limits<T>::lowest()});
if (next != nodes.end() && next->point.x == p.x) {
if (upper ? p.y <= next->point.y : p.y >= next->point.y) return false;
auto old = next++;
erase(old);
}
auto turn = [&](P a, P b, P c) { auto value = cross(a, b, c); return upper ? -value : value; };
if (next != nodes.begin() && next != nodes.end() && turn(std::prev(next)->point, p, next->point) <= 0) return false;
auto i = nodes.insert(next, Node{p, next == nodes.end() ? p : next->point, next == nodes.end()});
if (next != nodes.end()) area += cross(p, next->point);
if (i != nodes.begin()) {
auto prev = std::prev(i);
if (next != nodes.end()) area -= cross(prev->point, next->point);
area += cross(prev->point, p);
prev->next = p, prev->last = false;
}
while (i != nodes.begin()) {
auto prev = std::prev(i);
if (prev == nodes.begin() || turn(std::prev(prev)->point, prev->point, p) > 0) break;
erase(prev);
}
while (std::next(i) != nodes.end()) {
auto j = std::next(i), k = std::next(j);
if (k == nodes.end() || turn(p, j->point, k->point) > 0) break;
erase(j);
}
return true;
}
int side(P p, bool upper) const {
if (nodes.empty() || p.x < nodes.begin()->point.x || p.x > nodes.rbegin()->point.x) return -1;
auto i = nodes.lower_bound(P{p.x, std::numeric_limits<T>::lowest()});
W value;
if (i != nodes.end() && i->point.x == p.x) value = W(p.y) - i->point.y;
else value = cross(std::prev(i)->point, i->point, p);
if (upper) value = -value;
return (value > 0) - (value < 0);
}
};
Chain lower_, upper_;
static void check(P p) {
[[maybe_unused]] constexpr W limit = W(1000000000000000000LL);
assert(-limit <= W(p.x) && W(p.x) <= limit && -limit <= W(p.y) && W(p.y) <= limit);
}
std::optional<P> support_impl(Direction d) const {
if (empty()) return std::nullopt;
const auto &nodes = d.y < 0 ? lower_.nodes : upper_.nodes;
if (!d.y) return d.x > 0 ? nodes.rbegin()->point : nodes.begin()->point;
return nodes.lower_bound(d)->point;
}
public:
using point_type = P;
using value_type = W;
DynamicConvexHull() = default;
explicit DynamicConvexHull(const std::vector<P> &points) { for (auto p : points) insert(p); }
DynamicConvexHull(const DynamicConvexHull &) = default;
DynamicConvexHull(DynamicConvexHull &&other) noexcept { swap(other); }
DynamicConvexHull &operator=(DynamicConvexHull other) noexcept { swap(other); return *this; }
void swap(DynamicConvexHull &other) noexcept {
lower_.nodes.swap(other.lower_.nodes), upper_.nodes.swap(other.upper_.nodes);
std::swap(lower_.area, other.lower_.area), std::swap(upper_.area, other.upper_.area);
}
friend void swap(DynamicConvexHull &a, DynamicConvexHull &b) noexcept { a.swap(b); }
bool empty() const { return lower_.nodes.empty(); }
std::size_t size() const {
if (empty()) return 0;
auto result = lower_.nodes.size() + upper_.nodes.size();
bool first = lower_.nodes.begin()->point == upper_.nodes.begin()->point;
bool last = lower_.nodes.rbegin()->point == upper_.nodes.rbegin()->point;
return result - first - (last && result > 2);
}
void clear() { lower_.nodes.clear(); upper_.nodes.clear(); lower_.area = upper_.area = 0; }
bool insert(P p) { check(p); bool a = lower_.insert(p, false), b = upper_.insert(p, true); return a || b; }
std::vector<P> vertices() const {
std::vector<P> result;
result.reserve(size());
for (const auto &node : lower_.nodes) result.push_back(node.point);
for (auto i = upper_.nodes.rbegin(); i != upper_.nodes.rend(); ++i)
if (result.empty() || result.back() != i->point) result.push_back(i->point);
if (result.size() > 1 && result.front() == result.back()) result.pop_back();
return result;
}
W doubled_area() const {
if (empty()) return 0;
return lower_.area - upper_.area + cross(lower_.nodes.rbegin()->point, upper_.nodes.rbegin()->point)
+ cross(upper_.nodes.begin()->point, lower_.nodes.begin()->point);
}
Location contains(P p) const {
check(p);
if (lower_.side(p, false) < 0 || upper_.side(p, true) < 0) return Location::outside;
if (p.x == lower_.nodes.begin()->point.x || p.x == lower_.nodes.rbegin()->point.x
|| !lower_.side(p, false) || !upper_.side(p, true) || !doubled_area()) return Location::boundary;
return Location::inside;
}
std::optional<P> support(P direction) const { check(direction); return support_impl(Direction{direction.x, direction.y}); }
std::optional<W> max_dot(P direction) const {
auto p = support(direction);
return p ? std::optional<W>(dot(*p, direction)) : std::nullopt;
}
std::optional<W> min_dot(P direction) const {
check(direction);
auto p = support_impl(Direction{-W(direction.x), -W(direction.y)});
return p ? std::optional<W>(dot(*p, direction)) : std::nullopt;
}
};
}