Point Location

For non-crossing segments and query points, finds for every query the first segment hit by a ray shot straight down.

vector<int> below = segmentBelow(segs, queries);   // segs: vector<pair<P,P>>, no two cross
                                                    // (shared endpoints allowed), no vertical ones;
                                                    // below[i] = index or -1; a point on a segment hits it

Complexity: .

Sweep over ; active segments are ordered by height, which never changes between two segments because they do not cross, so a std::set with a geometric comparator stays valid. At each : answer the queries, remove the segments ending there, insert those starting there, answer again and keep the higher hit (two segments meeting at a point are never in the set together). Endpoints count as hits.

vector<int> segmentBelow(const vector<pair<P, P>>& input, const vector<P>& queries) {
    int n = input.size(), q = queries.size();
    vector<pair<P, P>> segs(input);
    for (auto& [a, b] : segs) if (b < a) swap(a, b);
    // segments in the set always share an open x-interval, so "b above the line of a" is a valid order;
    // indices >= n stand for query points
    auto less = [&](int a, int b) {
        auto pick = [&](int i) { return i < n ? segs[i] : pair{queries[i - n], queries[i - n]}; };
        auto [p1, q1] = pick(a);
        auto [p2, q2] = pick(b);
        int v = orient(p1, q1, p2) + orient(p1, q1, q2);
        if (a < n && v != 0) return v > 0;
        v = orient(p2, q2, p1) + orient(p2, q2, q1);
        if (b < n && v != 0) return v < 0;
        return a < b;
    };
    auto heightLess = [&](int a, int b, ll x) {   // height of segment a at x < height of b at x
        auto [pa, qa] = segs[a];
        auto [pb, qb] = segs[b];
        i128 na = (i128)pa.y * (qa.x - pa.x) + (i128)(qa.y - pa.y) * (x - pa.x), da = qa.x - pa.x;
        i128 nb = (i128)pb.y * (qb.x - pb.x) + (i128)(qb.y - pb.y) * (x - pb.x), db = qb.x - pb.x;
        return na * db < nb * da;
    };
    set<int, decltype(less)> active(less);
    vector<ll> xs;
    for (auto& [a, b] : segs) { xs.push_back(a.x); xs.push_back(b.x); }
    for (auto& p : queries) xs.push_back(p.x);
    sort(xs.begin(), xs.end());
    xs.erase(unique(xs.begin(), xs.end()), xs.end());
    int m = xs.size();
    vector<vector<int>> starts(m), ends(m), asks(m);
    auto at = [&](ll x) { return lower_bound(xs.begin(), xs.end(), x) - xs.begin(); };
    for (int i = 0; i < n; i++) { starts[at(segs[i].first.x)].push_back(i); ends[at(segs[i].second.x)].push_back(i); }
    for (int i = 0; i < q; i++) asks[at(queries[i].x)].push_back(i);
    vector<int> res(q, -1);
    auto best = [&](int i) {   // highest active segment not above query i
        auto it = active.upper_bound(n + i);
        return it == active.begin() ? -1 : *prev(it);
    };
    for (int k = 0; k < m; k++) {
        for (int i : asks[k]) res[i] = best(i);       // segments passing through here
        for (int s : ends[k]) active.erase(s);
        for (int s : starts[k]) active.insert(s);
        for (int i : asks[k]) {                       // segments starting here
            int c = best(i);
            if (c != -1 && (res[i] == -1 || heightLess(res[i], c, xs[k]))) res[i] = c;
        }
    }
    return res;
}

Related: planar subdivision faces — label every segment with the face above it, then the face of a point is the label of the segment below (or the outer face). Vertical walls: handle with a second structure per .

Tested against brute force: for every query, scan all segments whose -range contains the query’s , compute each one’s exact height there (as a fraction, compared cross-multiplied to avoid floating point), and take the highest one at or below the query — on random sets of non-crossing segments (built by generating random segments and discarding any crossing pair) and random query points, including queries exactly on a segment and exactly at a segment endpoint.

Points in Triangle

Number of points of a set strictly inside the triangle for many queries over vertices from a set .

TrianglePointCounter tc(A, B);         // vector<P>; duplicates / collinear points allowed
int c = tc.count(i, j, k);             // 0 for degenerate triangles

Complexity: build and memory (, ); per query.

Lexicographic order acts as an infinitesimal rotation of the -order, so every point has its own “column” with no ties to special-case. below[i][j] = points strictly between and in that order and strictly below segment (found via an angular sweep around with a Fenwick tree over ‘s order); onSeg[i][j] = points strictly on that segment. With in lex order: if is above line -, the answer is ; otherwise (points of equal to ). (Library Checker “Count Points in Triangle”.)

struct TrianglePointCounter {
    int n;
    vector<P> A;
    vector<vector<int>> below, onSeg;   // A[i] <lex A[j]: B points lex-between, strictly below / on segment i-j
    vector<int> same;                   // B points equal to A[i]
 
    TrianglePointCounter(const vector<P>& a, vector<P> B)
        : n(a.size()), A(a), below(n, vector<int>(n, 0)), onSeg(n, vector<int>(n, 0)), same(n, 0) {
        sort(B.begin(), B.end());
        int m = B.size();
        auto rankOf = [&](const P& p) { return int(lower_bound(B.begin(), B.end(), p) - B.begin()); };
        vector<int> fen(m + 1);
        struct Item { P d; int type, id; };   // type 0 = vertex A[id], 1 = point B[id]
        for (int i = 0; i < n; i++) {
            same[i] = int(upper_bound(B.begin(), B.end(), A[i]) - B.begin()) - rankOf(A[i]);
            vector<Item> items;
            for (int j = 0; j < n; j++) if (A[i] < A[j]) items.push_back({A[j] - A[i], 0, j});
            for (int r = upper_bound(B.begin(), B.end(), A[i]) - B.begin(); r < m; r++) items.push_back({B[r] - A[i], 1, r});
            sort(items.begin(), items.end(), [&](const Item& x, const Item& y) {   // by angle, then distance
                ll c = cross(x.d, y.d);
                if (c != 0) return c > 0;
                if (x.d != y.d) return x.d < y.d;
                return x.type < y.type;
            });
            fill(fen.begin(), fen.end(), 0);
            for (int s = 0; s < (int)items.size();) {
                int e = s;
                while (e < (int)items.size() && cross(items[s].d, items[e].d) == 0) e++;
                int seen = 0;   // B points of this ray closer than the current item
                for (int t = s; t < e; t++) {
                    if (items[t].type == 1) { seen++; continue; }
                    int j = items[t].id, cnt = 0;
                    for (int x = rankOf(A[j]); x > 0; x -= x & -x) cnt += fen[x];
                    below[i][j] = cnt; onSeg[i][j] = seen;
                }
                for (int t = s; t < e; t++)
                    if (items[t].type == 1) for (int x = items[t].id + 1; x <= m; x += x & -x) fen[x]++;
                s = e;
            }
        }
    }
 
    int count(int i, int j, int k) const {
        array<int, 3> v = {i, j, k};
        sort(v.begin(), v.end(), [&](int x, int y) { return A[x] < A[y]; });
        auto [a, b, c] = v;
        if (A[a] == A[b] || A[b] == A[c]) return 0;
        ll side = cross(A[a], A[c], A[b]);
        if (side == 0) return 0;
        if (side > 0) return below[a][b] + below[b][c] - below[a][c] - onSeg[a][c];   // b above line a-c
        return below[a][c] - below[a][b] - below[b][c] - onSeg[a][b] - onSeg[b][c] - same[b];
    }
};

Tested against brute force: build with small random (duplicates and collinear points included), then for every triple count -points with `inTriangle` directly (checking strict interior, i.e. excluding the boundary) and compare.

Max Collinear

Largest number of the given integer points lying on one line (duplicates count separately).

int best = maxCollinear(pts);   // vector<P>

Complexity: . For every anchor , normalise the direction to each other point (divide by the gcd, fix the sign) and count equal directions; copies of the anchor join every line through it. For with an answer known to be large (), pick random anchors instead (randomised, ).

int maxCollinear(const vector<P>& pts) {
    int n = pts.size(), best = min(n, 2);
    for (int i = 0; i < n; i++) {
        map<pair<ll, ll>, int> dirs;
        int same = 0, top = 0;
        for (int j = 0; j < n; j++) {
            if (j == i) continue;
            ll dx = pts[j].x - pts[i].x, dy = pts[j].y - pts[i].y;
            if (dx == 0 && dy == 0) { same++; continue; }
            ll g = __gcd(llabs(dx), llabs(dy));
            dx /= g; dy /= g;
            if (dx < 0 || (dx == 0 && dy < 0)) { dx = -dx; dy = -dy; }
            top = max(top, ++dirs[{dx, dy}]);
        }
        best = max(best, 1 + same + top);
    }
    return n == 0 ? 0 : best;
}

Tested against brute-force (every pair of points defines a line; count how many of the remaining points are collinear with it via orient, take the max) on random point sets including duplicates and sets with many points on one line.

Dobkin-Kirkpatrick

A multi-resolution representation of a convex polytope that answers many queries in — polytope separation, extreme point in a direction, ray shooting, and distance between two convex polytopes.

The structure

Build a sequence of nested convex polytopes , where each is obtained from by:

  1. finding an independent set of vertices (no two adjacent) with bounded degree — a constant fraction of the vertices, since planar/3-polytope graphs have bounded average degree;
  2. deleting them and re-triangulating the resulting holes.

Each step removes a constant fraction of the vertices, so: the hierarchy has levels; the total size across all levels is (a geometric series); the coarsest level is a constant-size polytope (a tetrahedron).

How queries use it

Answer the query on the tiny top-level polytope in , then refine downwards: at each level, the answer on is within combinatorial distance of the answer on , because only an independent set of vertices was reinserted. So each level costs and there are levels, giving total.

QueryTime
Is a point inside the polytope?
Extreme vertex in direction
Intersection of a line with the polytope (ray shooting)
Separating plane between two convex polytopes
Distance between two convex polytopes
Tangent lines/planes from an external point

This is genuinely intricate to implement (maintaining the independent-set hierarchy and the per-level refinement maps); like Kirkpatrick-Seidel, it is presented here for the technique rather than as code to hand-roll in a contest.

The 2D analogue you will actually use

In two dimensions, all of this collapses into something much simpler: a convex polygon’s vertices, in order, form a cyclically unimodal sequence with respect to any linear objective (proved in Convex Polygon Queries). So:

  • extreme point in a direction extremeVertex, ;
  • point in convex polygon inConvex, ;
  • tangents from a point convexTangents, ;
  • line-polygon intersection lineHullIntersection, .

These are the 20-line routines contest problems actually need. The Dobkin-Kirkpatrick hierarchy is the machinery required to get the same guarantees in 3D, where “binary search on the boundary” no longer makes sense.

Complexity

Construction ; space ; query or .

The general technique

Building an -level hierarchy where each level is a constant-factor coarsening, then refining the answer level by level, is a broadly reusable idea:

  • Sparse tables and binary lifting — the same doubling in a different dress.
  • Fractional cascading — reuse a binary search across many sorted lists.
  • Borůvka-style contraction — geometric shrinking in graph algorithms.
  • Level-of-detail meshes in graphics — literally this structure.

Variants / Use Cases

  • Convex Hull — build the polytope first.
  • Seidel’s LP — the other route to low-dimensional geometric optimisation.
  • GJK algorithm — the practical collision-detection method for convex bodies; a different technique, the same problem.
  • 3D Geometry.