π³ Range Query Structures: The Super-Fast Answer Machine
Imagine you have a magical bookshelf that can instantly tell you the sum of any books you point at!
π― The Big Picture
Think of your data like a row of jars filled with candies. Someone keeps asking you:
- βHow many candies are in jars 3 to 7?β
- βHow many in jars 1 to 5?β
The slow way: Count each jar every single time. π΄
The smart way: Build a special helper structure that remembers answers! π
Today weβll learn two magical helpers:
- Segment Tree - A tree that knows everything
- Binary Indexed Tree (BIT) - A clever shortcut machine
π² Segment Tree Fundamentals
What is a Segment Tree?
Imagine youβre the manager of a candy store with 8 jars. Instead of counting jars every time a customer asks, you create a pyramid of helpers.
[Total: 1-8] β Top helper knows EVERYTHING
/ \
[Sum 1-4] [Sum 5-8] β These know half each
/ \ / \
[1-2] [3-4] [5-6] [7-8] β These know pairs
/ \ / \ / \ / \
1 2 3 4 5 6 7 8 β Actual jars (leaves)
The Magic: Each helper remembers the sum of their children!
Building a Segment Tree
Letβs say we have 4 jars with candies: [3, 1, 4, 2]
int[] arr = {3, 1, 4, 2};
int[] tree = new int[4 * 4]; // 4n size is safe
void build(int node, int start, int end) {
if (start == end) {
// Leaf node: just copy the value
tree[node] = arr[start];
} else {
int mid = (start + end) / 2;
// Build left child
build(2*node, start, mid);
// Build right child
build(2*node+1, mid+1, end);
// This node = sum of children
tree[node] = tree[2*node] + tree[2*node+1];
}
}
Visual Result:
[10] β 3+1+4+2 = 10
/ \
[4] [6] β (3+1) and (4+2)
/ \ / \
3 1 4 2 β Original values
Why Size 4n?
A segment tree for n elements needs at most 4n space. Think of it like buying extra storage boxes β better safe than sorry!
π Segment Tree Range Query
The Question Game
βWhatβs the sum from jar 1 to jar 3?β
Instead of adding arr[1] + arr[2] + arr[3], we ask our tree helpers!
How Range Query Works
The tree walks down and collects answers:
int query(int node, int start, int end,
int L, int R) {
// Case 1: Completely outside
if (R < start || end < L) {
return 0; // Contributes nothing
}
// Case 2: Completely inside
if (L <= start && end <= R) {
return tree[node]; // Use stored sum!
}
// Case 3: Partial overlap
int mid = (start + end) / 2;
int leftSum = query(2*node, start, mid, L, R);
int rightSum = query(2*node+1, mid+1, end, L, R);
return leftSum + rightSum;
}
Example: Query Sum(1, 2)
With array [3, 1, 4, 2] (0-indexed):
Query: L=1, R=2 (sum of positions 1 and 2)
Step 1: Root [0-3] - Partial overlap
Step 2: Left child [0-1] - Partial overlap
β Position 1 gives us: 1
Step 3: Right child [2-3] - Partial overlap
β Position 2 gives us: 4
Answer: 1 + 4 = 5 β
Time Complexity
| Operation | Naive Array | Segment Tree |
|---|---|---|
| Query | O(n) | O(log n) |
| Build | - | O(n) |
For 1 million elements:
- Naive: 1,000,000 operations per query π°
- Segment Tree: ~20 operations per query π
βοΈ Segment Tree Update
The Problem with Change
A customer adds candies to jar 2. Now our treeβs sums are wrong!
Old: [3, 1, 4, 2] β New: [3, 5, 4, 2]
We need to update the tree β but not rebuild everything!
Point Update: The Domino Effect
When one leaf changes, only its ancestors need updating:
void update(int node, int start, int end,
int idx, int val) {
if (start == end) {
// Found the leaf!
arr[idx] = val;
tree[node] = val;
} else {
int mid = (start + end) / 2;
if (idx <= mid) {
// Go left
update(2*node, start, mid, idx, val);
} else {
// Go right
update(2*node+1, mid+1, end, idx, val);
}
// Recalculate this node
tree[node] = tree[2*node] + tree[2*node+1];
}
}
Visual Update Example
Updating position 1 from 1 to 5:
BEFORE: AFTER:
[10] [14] β 4+10=14
/ \ / \
[4] [6] [8] [6] β 3+5=8
/ \ / \ / \ / \
3 1 4 2 3 5 4 2 β Changed!
β β
Update this! Only these 3 nodes changed
Only O(log n) nodes updated! The magic of trees! π
π Binary Indexed Tree (BIT)
A Lighter, Faster Friend
Also called Fenwick Tree, this is a cleverer way to do the same job with less memory and simpler code.
The Secret: Binary Magic
BIT uses a clever trick with binary numbers:
- Position 6 in binary =
110 - The rightmost
1bit tells us how many elements this position βcoversβ
Index (decimal) | Binary | Covers
1 | 0001 | 1 element
2 | 0010 | 2 elements
3 | 0011 | 1 element
4 | 0100 | 4 elements
5 | 0101 | 1 element
6 | 0110 | 2 elements
7 | 0111 | 1 element
8 | 1000 | 8 elements
The BIT Array
int[] BIT = new int[n + 1]; // 1-indexed!
Each BIT[i] stores the sum of a specific range ending at i.
β‘ BIT Operations
Operation 1: Get Prefix Sum
To find sum from 1 to i, we hop backwards removing the lowest set bit:
int getSum(int i) {
int sum = 0;
while (i > 0) {
sum += BIT[i];
i -= (i & (-i)); // Remove lowest set bit
}
return sum;
}
Example: Sum(1 to 7)
i = 7 (0111) β add BIT[7], then i = 6
i = 6 (0110) β add BIT[6], then i = 4
i = 4 (0100) β add BIT[4], then i = 0
Done! Only 3 steps for 7 elements!
The Magic Formula: i & (-i)
This extracts the lowest set bit:
7 = 0111
-7 = 1001 (two's complement)
7&-7= 0001 β Lowest set bit!
Operation 2: Update Value
When we change a value, we update all BIT positions that include it:
void update(int i, int delta) {
while (i <= n) {
BIT[i] += delta;
i += (i & (-i)); // Add lowest set bit
}
}
Example: Update position 3
i = 3 (0011) β update BIT[3], then i = 4
i = 4 (0100) β update BIT[4], then i = 8
i = 8 (1000) β update BIT[8], then i = 16
Done! Only 3 updates for an 8-element array!
Range Sum with BIT
To get sum from L to R:
int rangeSum(int L, int R) {
return getSum(R) - getSum(L - 1);
}
Example: Sum from 3 to 6
= getSum(6) - getSum(2)
= (sum 1-6) - (sum 1-2)
= sum 3-6 β
π₯ Segment Tree vs BIT: The Showdown
| Feature | Segment Tree | BIT |
|---|---|---|
| Memory | 4n | n+1 |
| Code complexity | Medium | Simple |
| Range update | β Easy | Tricky |
| Point update | β | β |
| Range query | β | β |
| Min/Max query | β | β |
| Build time | O(n) | O(n log n) |
When to Use What?
- Use BIT when: You only need sum/count queries and updates are point-based
- Use Segment Tree when: You need min/max, range updates, or complex operations
π¨ Complete Working Examples
Full Segment Tree Implementation
class SegmentTree {
int[] tree, arr;
int n;
SegmentTree(int[] input) {
n = input.length;
arr = input;
tree = new int[4 * n];
build(1, 0, n - 1);
}
void build(int node, int s, int e) {
if (s == e) {
tree[node] = arr[s];
return;
}
int mid = (s + e) / 2;
build(2*node, s, mid);
build(2*node+1, mid+1, e);
tree[node] = tree[2*node] + tree[2*node+1];
}
int query(int node, int s, int e, int L, int R) {
if (R < s || e < L) return 0;
if (L <= s && e <= R) return tree[node];
int mid = (s + e) / 2;
return query(2*node, s, mid, L, R) +
query(2*node+1, mid+1, e, L, R);
}
void update(int node, int s, int e, int i, int v) {
if (s == e) {
arr[i] = v;
tree[node] = v;
return;
}
int mid = (s + e) / 2;
if (i <= mid) update(2*node, s, mid, i, v);
else update(2*node+1, mid+1, e, i, v);
tree[node] = tree[2*node] + tree[2*node+1];
}
}
Full BIT Implementation
class BIT {
int[] tree;
int n;
BIT(int size) {
n = size;
tree = new int[n + 1];
}
void update(int i, int delta) {
for (; i <= n; i += i & (-i))
tree[i] += delta;
}
int query(int i) {
int sum = 0;
for (; i > 0; i -= i & (-i))
sum += tree[i];
return sum;
}
int rangeQuery(int L, int R) {
return query(R) - query(L - 1);
}
}
π§ Quick Memory Tricks
graph TD A["Need Range Queries?"] -->|Yes| B{What operation?} B -->|Sum/Count only| C["Use BIT - Simpler!"] B -->|Min/Max/Complex| D["Use Segment Tree"] A -->|No| E["Use simple array"]
Remember This Rhyme:
"BIT for sums, quick and light, Segment Tree for everything β day or night!"
π― Key Takeaways
- Segment Tree = Tree that pre-computes answers for all possible ranges
- Build once O(n), query forever O(log n)
- BIT = Clever use of binary numbers for prefix sums
i & (-i)= Magic formula for the lowest set bit- Both turn O(n) queries into O(log n) β‘
Youβve just learned the secret weapons of competitive programmers! π
Now go build some super-fast query machines! π
