Segment Trees & Fenwick (BIT)
Segment Tree ও Fenwick Tree
1. The Two Workhorses of Range Queries
Segment trees answer any associative range query (sum / min / max / gcd / xor) with point or range updates in O(log n). Fenwick (BIT) is a leaner cousin that handles prefix sums + point updates with much smaller code. Both are competitive-programming staples.
2. Iterative Segment Tree (Bottom-Up)
Lay leaves at indices [n, 2n). Internal node i holds op(t[2i], t[2i+1]).
Update bubbles up one path; query walks two pointers in from l = n + l
and r = n + r + 1, combining whenever an index is the odd / left sibling.
#include <bits/stdc++.h>
using namespace std;
int n;
long long t[400000];
void build(vector<int>& a) {
n = a.size();
for (int i = 0; i < n; i++) t[n + i] = a[i];
for (int i = n - 1; i > 0; i--) t[i] = t[2*i] + t[2*i + 1];
}
void update(int p, long long v) {
for (t[p += n] = v; p > 1; p >>= 1) t[p >> 1] = t[p] + t[p ^ 1];
}
long long query(int l, int r) { // [l, r)
long long res = 0;
for (l += n, r += n; l < r; l >>= 1, r >>= 1) {
if (l & 1) res += t[l++];
if (r & 1) res += t[--r];
}
return res;
}
int main() {
vector<int> a = {2, 5, 1, 4, 9, 3, 7, 6};
build(a);
cout << "sum[2,6) = " << query(2, 6) << "\n"; // 1+4+9+3 = 17
update(3, 100);
cout << "after a[3]=100, sum[0,8) = " << query(0, 8);
}
3. Fenwick / BIT — The Compact Cousin
BIT (Binary Indexed Tree) supports prefix-sum queries and point updates in O(log n) with just
two short loops. The idea: each index i is responsible for a range of length equal to its
lowest set bit — accessed via i & -i.
#include <bits/stdc++.h>
using namespace std;
struct BIT {
vector<long long> b;
int n;
BIT(int n) : b(n + 1, 0), n(n) {}
void add(int i, long long v) { for (++i; i <= n; i += i & -i) b[i] += v; }
long long sum(int i) { // prefix sum a[0..i]
long long r = 0;
for (++i; i > 0; i -= i & -i) r += b[i];
return r;
}
long long range(int l, int r) { return sum(r) - (l ? sum(l - 1) : 0); }
};
int main() {
vector<int> a = {3, 1, 4, 1, 5, 9, 2, 6};
BIT bit(a.size());
for (int i = 0; i < (int)a.size(); i++) bit.add(i, a[i]);
cout << "sum[2..5] = " << bit.range(2, 5) << "\n"; // 4+1+5+9=19
bit.add(3, 10); // a[3] += 10
cout << "after add, sum[2..5] = " << bit.range(2, 5);
}
4. Lazy Propagation — Range Update + Range Query
Plain segment trees handle point-update + range-query in O(log n). For range-update + range-query, we attach a lazy value at each node that represents a pending operation on the whole subrange. We push lazy down only when we descend into a child.
"আমার children-কে এখনো জানাইনি, কিন্তু আমার aggregate-এ +Δ যোগ হয়ে আছে।"
With lazy, range-add + range-sum becomes O(log n) per operation — essential for problems
like "increment a[l..r] by Δ then query sum(l..r)" on n = 10⁵, q = 10⁵.
5. Segment Tree vs BIT — Quick Compare
| Feature | Segment Tree | Fenwick (BIT) |
|---|---|---|
| Code length | ~30 lines | ~10 lines |
| Memory | ~4n | n + 1 |
| Operations supported | Any associative | Group-like (sum, xor) |
| Range update + range query | Yes, with lazy | Yes, with two BITs |
| Constant factor | Higher | Lower |
| Find kth / first ≥ x | Easy (descend) | Possible with bit tricks |
6. Practice Problems
-
Count inversions of an array using a BIT in O(n log n).BIT দিয়ে O(n log n)-এ inversions count করুন।
✨ Show Answer (উত্তর দেখুন)
a1.cpp#include <bits/stdc++.h> using namespace std; int n; vector<long long> b; void add(int i){ for(++i; i<=n; i+=i&-i) b[i]++; } long long qry(int i){ long long r=0; for(++i; i>0; i-=i&-i) r+=b[i]; return r; } int main() { vector<int> a = {8,4,2,1}; vector<int> s(a); sort(s.begin(), s.end()); s.erase(unique(s.begin(),s.end()),s.end()); n = s.size(); b.assign(n+1, 0); long long inv = 0; for (int i = a.size()-1; i >= 0; i--) { int r = lower_bound(s.begin(), s.end(), a[i]) - s.begin(); if (r > 0) inv += qry(r-1); add(r); } cout << inv; } -
Range max + point update on an iterative segment tree.Range max + point update — iterative segment tree।
✨ Show Answer (উত্তর দেখুন)
Change: replace
+withmax(...)and the identity from 0 toINT_MIN. Update bubbles up the same path. -
Range XOR query with point updates, on an iterative segment tree.Range XOR query — iterative segment tree।
✨ Show Answer (উত্তর দেখুন)
Change: use
^as the combine operation; identity = 0. The merge function is associative and has an identity, so it works on a segment tree (and on a BIT — XOR has inverses). -
Range add + range sum with lazy propagation on a recursive segment tree.Lazy দিয়ে range add + range sum।
✨ Show Answer (উত্তর দেখুন)
Approach: store
lazy[node]= pending add.push(node, l, r): if lazy ≠ 0, add lazy×len to children'stree, propagate lazy, clear here. Update / query: push first, then recurse, then re-aggregate. -
Implement a 2D BIT for sum queries on a 2D matrix.2D BIT — সম্পূর্ণ matrix range sum query।
✨ Show Answer (উত্তর দেখুন)
Sketch: two nested loops over
i & -iandj & -j. Update O(log²n), query O(log²n). The same shape as 1D BIT, just nested. -
Find the kth smallest element among inserted-so-far values, online, using a BIT over a coordinate-compressed range.Online kth smallest — BIT + coordinate compression।
✨ Show Answer (উত্তর দেখুন)
Approach: compress all possible values to [0, m). Maintain BIT-of-counts. To find kth, walk down BIT bits high → low: jump if remaining k > count in that block; this is O(log m) descent.
-
Why is BIT slightly faster than segment tree in practice, even though both are O(log n)?BIT সাধারণত একটু দ্রুত কেন?
✨ Show Answer (উত্তর দেখুন)
Answer: smaller code → better cache behaviour; one tight loop, no recursion, no large 4n array, no lazy bookkeeping. Constant factor maybe ~3× smaller. Trade-off: BIT only does inverse-friendly operations like sum/xor.
Summary — Module 23
Segment tree: any associative range query + point/range update in O(log n). Use lazy propagation for range updates. Fenwick / BIT: ultra-compact prefix-sum + point-update, still O(log n), and easily extended to range update + range query with two BITs. Master both — they are the most-asked topics in any competitive contest.