Data Structures contents
Sparse Table
Answer range-minimum (and other idempotent) queries in O(1) after O(n log n) preprocessing, for arrays that never change.
Read first: Bit Manipulation
A sparse table answers range queries on a static array (no updates). For an idempotent operation (one where applying it twice to the same element changes nothing: min, max, gcd, and, or) each query is answered in , after preprocessing time and memory.
Idea
Precompute the answer for every segment whose length is a power of two. Let table[k][i] be the result for the segment . Level is built from level by combining two halves:
Level 0 is the array itself. There are levels.
Range minimum queries in
For a query of length , let . Two segments of length — one starting at , one ending at — cover the query, possibly overlapping. Because min is idempotent, counting the overlap twice does not matter:
class SparseTable:
def __init__(self, data, op=min):
self.op = op
n = len(data)
self.table = [list(data)]
k = 1
while (1 << k) <= n:
prev = self.table[-1]
half = 1 << (k - 1)
self.table.append(
[op(prev[i], prev[i + half]) for i in range(n - (1 << k) + 1)]
)
k += 1
def query(self, l, r):
"""op over a[l:r] (half-open, requires l < r)."""
k = (r - l).bit_length() - 1
row = self.table[k]
return self.op(row[l], row[r - (1 << k)])
from math import gcd
import random
a = [7, 2, 3, 0, 5, 10, 3, 12, 18]
st = SparseTable(a)
assert st.query(0, 9) == 0
assert st.query(4, 8) == 3
assert st.query(5, 6) == 10
random.seed(1)
for _ in range(300):
n = random.randint(1, 40)
arr = [random.randint(-50, 50) for _ in range(n)]
for op, name in ((min, "min"), (max, "max")):
table = SparseTable(arr, op)
for _ in range(20):
l = random.randint(0, n - 1)
r = random.randint(l + 1, n)
assert table.query(l, r) == op(arr[l:r])
g = SparseTable([12, 18, 30, 8, 20], gcd)
assert g.query(0, 3) == 6 and g.query(3, 5) == 4(r - l).bit_length() - 1 is computed in .
Range sum queries (not idempotent)
For sums the overlap would be counted twice, so decompose the segment into non-overlapping power-of-two pieces, one per set bit of its length. That costs per query:
def build_sum_table(data):
table = [list(data)]
k = 1
while (1 << k) <= len(data):
prev, half = table[-1], 1 << (k - 1)
table.append([prev[i] + prev[i + half] for i in range(len(data) - (1 << k) + 1)])
k += 1
return table
def range_sum(table, l, r):
total = 0
for k in range(len(table) - 1, -1, -1):
if (r - l) >> k & 1:
total += table[k][l]
l += 1 << k
return total
arr = [3, 1, 4, 1, 5, 9, 2, 6]
t = build_sum_table(arr)
assert all(range_sum(t, l, r) == sum(arr[l:r]) for l in range(8) for r in range(l, 9))Prefix sums do the same job in with memory, so the sparse table is only the right tool for idempotent operations.
Comparison
| Structure | Build | Query | Update |
|---|---|---|---|
| prefix sums | (sum only) | ||
| sparse table | (idempotent) | rebuild | |
| segment tree | |||
| Fenwick tree |
Practice problems
- SPOJ - RMQSQ
- SPOJ - THRBL
- Codechef - MSTICK
- Codechef - SEAD
- Codeforces - CGCDSSQ
- Codeforces - R2D2 and Droid Army
- Codeforces - Maximum of Maximums of Minimums
- SPOJ - Miraculous
- DevSkill - Multiplication Interval (archived)
- Codeforces - Animals and Puzzles
- Codeforces - Trains and Statistics
- SPOJ - Postering
- SPOJ - Negative Score
- SPOJ - A Famous City
- SPOJ - Diferencija
- Codeforces - Turn off the TV
- Codeforces - Map
- Codeforces - Awards for Contestants
- Codeforces - Longest Regular Bracket Sequence
- CSES - Static Range Minimum Queries
- Codeforces - Array Stabilization (GCD version)