Range queries over a grid. The structures generalise, but the memory grows quickly, so choose carefully.
2D Fenwick tree
A BIT of BITs. Point update, prefix-rectangle query, both .
struct BIT2D {
int n, m;
vector<vector<long long>> t;
BIT2D(int n, int m) : n(n), m(m), t(n + 1, vector<long long>(m + 1, 0)) {}
void update(int x, int y, long long v) { // 1-indexed
for (int i = x; i <= n; i += i & -i)
for (int j = y; j <= m; j += j & -j)
t[i][j] += v;
}
long long query(int x, int y) { // prefix sum of [1..x][1..y]
long long s = 0;
for (int i = x; i > 0; i -= i & -i)
for (int j = y; j > 0; j -= j & -j)
s += t[i][j];
return s;
}
long long rect(int x1, int y1, int x2, int y2) {
return query(x2, y2) - query(x1 - 1, y2) - query(x2, y1 - 1) + query(x1 - 1, y1 - 1);
}
};Memory — fine for , impossible for .
2D segment tree
A segment tree over rows, where each node holds a segment tree over columns.
| Build | Query | Update | Memory | |
|---|---|---|---|---|
| 2D BIT | ||||
| 2D segment tree | ||||
| 2D segment tree, sparse | — |
No lazy propagation in 2D
Range updates on a 2D segment tree with lazy propagation do not work in general — a lazy tag on a row node cannot be pushed into the column trees consistently. Options: a 2D BIT with the four-BIT range-update trick (sums only), a sqrt decomposition, or offline processing.
When the grid is sparse
With up to but only points, an structure is impossible. The options:
1. Compress both coordinates. If all queries are known offline, map the distinct and values to and use a dense structure.
2. Merge sort tree / BIT of sorted vectors. Sort points by ; each BIT node stores the values of its range, sorted. Query with binary search inside each node. per query, memory, no updates.
3. Offline sweep + 1D BIT. Sort everything by and sweep, maintaining a 1D BIT over . This is almost always the right answer: time, memory, and it handles updates.
4. Persistent segment tree. Version contains all points with ; a rectangle query is the difference of two versions. Online, per query, memory.
5. KD-tree. per rectangle query, memory, and it supports insertions and nearest-neighbour queries.
The offline sweep — usually the answer
// count points inside each query rectangle
// decompose each rectangle into 2 prefix queries via inclusion-exclusion on x
sort(events.begin(), events.end()); // by x
BIT bit(maxY);
for (auto& e : events) {
if (e.isPoint) bit.update(e.y, 1);
else ans[e.qid] += e.sign * bit.query(e.y1, e.y2);
}with memory — strictly better than a 2D structure on both counts whenever the queries can be sorted.
The decision table
| Grid size | Updates | Online? | Structure |
|---|---|---|---|
| point | yes | 2D BIT | |
| rectangle add, rectangle sum | yes | four 2D BITs | |
| large, sparse | none | no | offline sweep + 1D BIT |
| large, sparse | none | yes | persistent segment tree |
| large, sparse | point | no | offline sweep (CDQ if 3 dimensions) |
| large, sparse | point | yes | sparse 2D segment tree, or KD-tree |
| 3+ dimensions | any | no | CDQ divide and conquer |
See also: Fenwick Tree · Merge Sort Tree · 2D Queries