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
| Grid | Updates | Online? | Structure | Cost |
|---|---|---|---|---|
| Dense, | none | ✔ | 2D prefix sum | query |
| Dense, | point | ✔ | 2D BIT | |
| Dense | rectangle add + rectangle sum | ✔ | four 2D BITs | |
| Sparse, large | none | ✘ | offline sweep + BIT | |
| Sparse, large | none | ✔ | persistent segment tree | |
| Sparse, large | point | ✘ | offline sweep, or CDQ | |
| Sparse, large | point | ✔ | sparse 2D segment tree, or KD-tree | / |
| 3+ dimensions | any | ✘ | 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
| Structure | Memory |
|---|---|
| 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