Queries over a grid or a point set: rectangle sums, counts, dominance. The choice of structure depends almost entirely on whether the queries can go offline.

The decision table

GridUpdatesOnline?StructureCost
Dense, none✔2D prefix sum query
Dense, point✔2D BIT
Denserectangle add + rectangle sum✔four 2D BITs
Sparse, largenone✘offline sweep + BIT
Sparse, largenone✔persistent segment tree
Sparse, largepoint✘offline sweep, or CDQ
Sparse, largepoint✔sparse 2D segment tree, or KD-tree /
3+ dimensionsany✘CDQ, nested
Non-rectangular regions—✔KD-tree

Go offline

The single most useful fact here: sort by one coordinate and sweep with a 1D BIT answers most 2D query problems in time and memory — better than any 2D structure on both axes. Reach for a genuinely 2D structure only when the queries must be online.

The offline sweep

Decompose each rectangle query into two (or four) prefix queries by inclusion-exclusion, then sort everything by :

// count points inside each query rectangle
struct Ev { int x, y1, y2, id, sign; bool isPoint; };
sort(ev.begin(), ev.end(), [](const Ev& a, const Ev& b) {
    return a.x != b.x ? a.x < b.x : a.isPoint > b.isPoint;   // points before queries
});
BIT bit(maxY);
for (auto& e : ev) {
    if (e.isPoint) bit.add(e.y1, 1);
    else ans[e.id] += e.sign * bit.rangeSum(e.y1, e.y2);
}

The tie-break (points before queries at the same ) encodes whether the boundary is inclusive — get it wrong and off-by-one errors appear only on specific inputs.

2D prefix sums


The four-term formula is inclusion-exclusion; in dimensions it becomes terms. Beyond the constant makes it impractical.

The 2D difference array is the dual (rectangle update, read at the end) — see Difference Array.

Dominance counting

“For each point, how many others have both coordinates it?”

Sort by , sweep, and count with a BIT over . .

In three dimensions this becomes the 3D partial order problem, solved by CDQ in — sort by , CDQ on , BIT on .

Rectangle union

Area, perimeter, or “how many rectangles cover each point”: a sweep line with a segment tree storing per-node counts and covered length. . This tree needs no lazy propagation, because updates arrive in matching pairs.

KD-trees and their niche

A KD-tree answers rectangle queries in and nearest-neighbour in expected, with memory, and supports insertions with periodic rebuilds.

Its real advantage is non-rectangular regions — circles, half-planes, arbitrary convex shapes — which BIT-based sweeps cannot express. For plain rectangles, the offline sweep wins.

Memory, the real constraint

StructureMemory
2D prefix / 2D BIT
Merge sort tree / BIT of vectors
Persistent segment tree ints
Offline sweep + BIT
KD-tree
2D segment tree (sparse)

A long long 2D BIT is 32 MB — near the limit. Above that, compress or go offline.

Higher dimensions

Each extra dimension costs a (CDQ) or makes range queries (KD-tree). Beyond or 4, essentially every structure degenerates to a linear scan, and the right move is to find a reformulation that removes a dimension — often by sorting.

See also: 2D Fenwick · CDQ · Sweep Line