Maintains the convex hull of a growing point set: insert a point, test whether a point is inside or on the hull, in amortized per operation.

DynamicHull h;
h.insert(P{x, y});
bool in = h.contains(p);           // inside or on the boundary of the current hull
vector<P> v = h.vertices();        // CCW, strict vertices, starting at the leftmost-lowest

Idea: keep the upper and lower hulls separately, each as a map<ll,ll> from to (the lower hull is stored as the upper hull of the points reflected through the -axis, i.e. , so both chains use the same code). A new point outside a chain removes the neighbours on each side that stop being convex, exactly as in the stack-popping step of monotone chain, but anchored by a BST instead of an array so insertion doesn’t need a sorted-from-scratch pass. Coordinates up to keep the cross products in the proof below inside __int128 comfortably.

struct UpperHull {
    map<ll, ll> pts;                                       // x -> y, a strictly concave chain
 
    static i128 turn(ll ax, ll ay, ll bx, ll by, ll cx, ll cy) {  // cross(b - a, c - a)
        return (i128)(bx - ax) * (cy - ay) - (i128)(by - ay) * (cx - ax);
    }
    bool below(ll x, ll y) const {                          // (x, y) on or under the chain
        if (pts.empty()) return false;
        auto it = pts.lower_bound(x);
        if (it == pts.end()) return false;                  // right of the whole chain
        if (it->first == x) return y <= it->second;
        if (it == pts.begin()) return false;                // left of the whole chain
        auto pv = prev(it);
        return turn(pv->first, pv->second, it->first, it->second, x, y) <= 0;
    }
    void insert(ll x, ll y) {
        if (below(x, y)) return;                             // strictly under: no change needed
        pts[x] = y;
        auto it = pts.find(x);
        while (it != pts.begin()) {                          // remove left neighbours no longer on the chain
            auto a = prev(it);
            if (a == pts.begin()) break;
            auto b = prev(a);
            if (turn(b->first, b->second, a->first, a->second, x, y) >= 0) pts.erase(a);
            else break;
        }
        while (next(it) != pts.end()) {                      // remove right neighbours no longer on the chain
            auto a = next(it), b = next(a);
            if (b == pts.end()) break;
            if (turn(x, y, a->first, a->second, b->first, b->second) >= 0) pts.erase(a);
            else break;
        }
    }
};
 
struct DynamicHull {
    UpperHull upper, lower;                                  // lower stores (x, -y)
 
    void insert(P p) { upper.insert(p.x, p.y); lower.insert(p.x, -p.y); }
    bool contains(P p) const { return upper.below(p.x, p.y) && lower.below(p.x, -p.y); }
    vector<P> vertices() const {
        vector<P> res;
        for (auto& [x, y] : lower.pts) res.push_back({x, -y});
        for (auto it = upper.pts.rbegin(); it != upper.pts.rend(); ++it) {
            P p{it->first, it->second};
            if (!res.empty() && (p == res.back() || p == res.front())) continue;
            res.push_back(p);
        }
        return res;
    }
};

Complexity: amortized per insert and query. Deletions: offline divide and conquer over time (if all operations are known in advance), or the Overmars-van Leeuwen structure (a BST of hull edges with each node storing enough to support true online deletion) — both are substantial, and avoidable in most contest problems (see the escape hatches).

Tested: DynamicHull built by inserting random points one at a time; after every insertion, vertices() is checked against monotone chain recomputed from scratch on all points inserted so far (same vertex set), and contains is checked against the from-scratch hull’s inConvex/inPolygon test on a grid of query points.