Sparse Tables & Range Queries
Sparse Table ও Range query
1. The Static-Data Niche
When the array never changes and we only ask range minimum / maximum / gcd queries, we can do O(1) per query with O(n log n) preprocessing using a sparse table. This is faster than segment tree's O(log n) per query.
2. Idempotent Functions
A function op is idempotent if op(x, x) = x.
Examples: min, max, gcd, bitwise AND, bitwise OR. For these, overlapping ranges in a query
don't double-count — we can answer with two precomputed power-of-2 windows.
sum কাজ করবে না (overlap ডবল গণনা হবে), কিন্তু min/max/gcd করবে।
3. Building & Querying a Sparse Table
st[k][i] = min of a[i .. i + 2ᵏ − 1]. Recurrence:
st[k][i] = min(st[k−1][i], st[k−1][i + 2^(k−1)]). Total O(n log n) time and space.
#include <bits/stdc++.h>
using namespace std;
struct SparseMin {
vector<vector<int>> st;
vector<int> lg;
SparseMin(vector<int>& a) {
int n = a.size();
int K = log2(n) + 1;
st.assign(K, vector<int>(n));
st[0] = a;
for (int k = 1; (1 << k) <= n; k++)
for (int i = 0; i + (1 << k) <= n; i++)
st[k][i] = min(st[k-1][i], st[k-1][i + (1 << (k-1))]);
lg.assign(n + 1, 0);
for (int i = 2; i <= n; i++) lg[i] = lg[i / 2] + 1;
}
int query(int l, int r) { // inclusive [l, r]
int k = lg[r - l + 1];
return min(st[k][l], st[k][r - (1 << k) + 1]);
}
};
int main() {
vector<int> a = {7, 2, 3, 0, 5, 10, 3, 12, 18};
SparseMin sm(a);
cout << "min[2..7] = " << sm.query(2, 7) << "\n"; // 0
cout << "min[5..8] = " << sm.query(5, 8); // 3
}
4. Square-Root Decomposition
Split the array into blocks of size √n. Pre-aggregate each block. A range query touches at most O(√n) full blocks + O(√n) edge elements → O(√n) per query. Updates are O(1) for the element + O(√n) for the block aggregate → O(√n).
5. Mo's Algorithm — Offline Queries in O((n+q)·√n)
For offline range queries on static data, Mo's algorithm sorts queries by (block, r) and walks two pointers — adding / removing elements — to amortise to O((n + q)·√n). Used for "count distinct in [l, r]", "frequency-of-mode", etc.
Online vs Offline
- Online: queries arrive one at a time, must be answered immediately
- Offline: all queries known up front, can be reordered freely
Mo's pre-conditions
- Static array
- Add / remove at the ends in O(1) (or O(log n))
- All queries known upfront
6. Practice Problems
-
Range minimum query on the array
[3, 1, 4, 1, 5, 9, 2, 6]for [2..6].Sparse table দিয়ে min[2..6] বের করুন।✨ Show Answer (উত্তর দেখুন)
Answer: values are 4, 1, 5, 9, 2 → min = 1. With sparse table: len = 5, k = 2, min(st[2][2], st[2][3]) = min(min(4,1,5,9), min(1,5,9,2)) = min(1, 1) = 1.
-
Range GCD query — modify the sparse table to compute gcd.Range GCD query — sparse table-এ পরিবর্তন।
✨ Show Answer (উত্তর দেখুন)
Change: replace
min(...)with__gcd(...). GCD is idempotent:gcd(x, x) = x— overlap is fine. -
Why does sum NOT work on a sparse table?Sum কেন sparse table-এ চলে না?
✨ Show Answer (উত্তর দেখুন)
Answer: the two overlapping power-of-2 windows would double-count elements in the overlap. Sum is not idempotent (sum(x, x) = 2x, not x). For sum on a static array, use a prefix-sum array — O(1) query, no overlap issue.
-
Static range mode (most frequent value) using Mo's algorithm — sketch.Static range mode — Mo's algorithm দিয়ে।
✨ Show Answer (উত্তর দেখুন)
Sketch: sort queries by (l / √n, r). Maintain
cnt[value]andfreq[count]. On add: cnt[v]++; freq[cnt[v] − 1]−−; freq[cnt[v]]++; track maxCount. On remove: reverse. After processing each window, mode count = maxCount. O((n+q)√n). -
Count distinct values in [l, r] for offline queries — outline.Range distinct count — Mo's algorithm।
✨ Show Answer (উত্তর দেখুন)
Outline: Mo's, with cnt[v]++; if cnt[v] == 1 → distinct++. On remove: cnt[v]−−; if cnt[v] == 0 → distinct−−. Answer is distinct.
-
If the array is dynamic (point updates allowed), would you still use a sparse table? Why or why not?Update থাকলে sparse table কেন কাজ করবে না?
✨ Show Answer (উত্তর দেখুন)
Answer: a point update at index i invalidates O(log n) entries per level — total O(log² n) entries to fix per update, but in the worst case across the table this becomes O(n log n) per update. Use a segment tree instead — O(log n) per update.
Summary — Module 24
Sparse table: O(n log n) preprocess + O(1) query for idempotent ops on static data. Square-root decomposition: simpler O(√n) range structures with updates. Mo's algorithm: offline queries in O((n + q)√n). Each tool fits a different update / query pattern — choose by data movement, not by complexity alone.