Segment Trees & Fenwick (BIT)

Segment Tree ও Fenwick Tree

Read: ~50 min Advanced 7 practice problems Live code runner

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.

Range query + update — দুটিই O(log n)-এ চাইলে segment tree বা BIT। Segment tree বেশি general; BIT prefix-sum-এর জন্য বেশি compact। ICPC-তে দুটিই অপরিহার্য।

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.

segtree_iter.cpp
#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.

bit.cpp
#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.

Lazy idea "I owe my children +Δ. I haven't told them yet — but my own aggregate already includes it." When a query or update wants to enter a child, push the lazy down first.
"আমার 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

FeatureSegment TreeFenwick (BIT)
Code length~30 lines~10 lines
Memory~4nn + 1
Operations supportedAny associativeGroup-like (sum, xor)
Range update + range queryYes, with lazyYes, with two BITs
Constant factorHigherLower
Find kth / first ≥ xEasy (descend)Possible with bit tricks

6. Practice Problems

  1. 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;
    }
  2. Range max + point update on an iterative segment tree.
    Range max + point update — iterative segment tree।
    ✨ Show Answer (উত্তর দেখুন)

    Change: replace + with max(...) and the identity from 0 to INT_MIN. Update bubbles up the same path.

  3. 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).

  4. 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's tree, propagate lazy, clear here. Update / query: push first, then recurse, then re-aggregate.

  5. 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 & -i and j & -j. Update O(log²n), query O(log²n). The same shape as 1D BIT, just nested.

  6. 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.

  7. 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.

Segment tree বেশি general; BIT compact ও দ্রুত। দুটিই ICPC-তে অপরিহার্য — মুখস্থ template তৈরি রাখুন।

Next Module → Sparse Tables & Range Queries — static data, O(1) RMQ।