Segment Trees
- Segment Tree with lazy propagation to efficiently handle range updates and range minimum queries (RMQ).
- Segment trees provide fast queries and updates, making them ideal for dynamic array problems.
- to get RSQ in Segment Tree
- In build function replace :
st[p] = min(st[2*p], st[2*p+1]);
with st[p] = st[2*p] + st[2*p+1];
- In query function :
min(query(...), query(...));
with query(...left...) + query(...right...);
- In update function :
st[p] = st[2*p] + st[2*p+1];
Operation |
Fenwick Tree |
Segment Tree |
Point Update |
✅ O(log n) |
✅ O(log n) |
Range Query |
✅ O(log n) |
✅ O(log n) |
Range Update |
❌* |
✅ (needs lazy) |
Point Query |
❌* |
✅ |
Segment Tree (without Lazy Propagation)
- Only Point Updates are available
#include <bits/stdc++.h>
using namespace std;
class SegmentTree {
int n;
vector<int> st;
int l(int p) { return p<<1; }
int r(int p) { return (p<<1)+1; }
void build(const vector<int>& A, int p, int l, int r) {
if (l == r)
st[p] = A[l];
else {
int m = (l + r) / 2;
build(A, l(p), l, m);
build(A, r(p), m+1, r);
st[p] = min(st[l(p)], st[r(p)]);
}
}
int query(int p, int l, int r, int i, int j) {
if (j < l || i > r) return INT_MAX;
if (i <= l && r <= j) return st[p];
int m = (l + r) / 2;
return min(query(l(p), l, m, i, j), query(r(p), m+1, r, i, j));
}
// notice its a point update
void update(int p, int l, int r, int idx, int val) {
if (l == r)
st[p] = val;
else {
int m = (l + r) / 2;
if (idx <= m) update(l(p), l, m, idx, val);
else update(r(p), m+1, r, idx, val);
st[p] = min(st[l(p)], st[r(p)]);
}
}
public:
SegmentTree(const vector<int>& A) {
n = A.size();
st.assign(4*n, 0);
build(A, 1, 0, n-1);
}
int query(int i, int j) { return query(1, 0, n-1, i, j); }
void update(int idx, int val) { update(1, 0, n-1, idx, val); }
};
Segment Tree with Lazy Propagation (Optimal)
#include <bits/stdc++.h>
using namespace std;
typedef vector<int> vi;
class SegmentTree { // OOP style
private:
int n; // n = (int)A.size()
vi A, st, lazy; // the arrays
int l(int p) { return p<<1; } // go to left child
int r(int p) { return (p<<1)+1; } // go to right child
int conquer(int a, int b) {
if (a == -1) return b; // corner case
if (b == -1) return a;
return min(a, b); // RMQ
}
void build(int p, int L, int R) { // O(n)
if (L == R)
st[p] = A[L]; // base case
else {
int m = (L+R)/2;
build(l(p), L , m);
build(r(p), m+1, R);
st[p] = conquer(st[l(p)], st[r(p)]);
}
}
void propagate(int p, int L, int R) {
if (lazy[p] != -1) { // has a lazy flag
st[p] = lazy[p]; // [L..R] has same value
if (L != R) // not a leaf
lazy[l(p)] = lazy[r(p)] = lazy[p]; // propagate downwards
else // L == R, a single index
A[L] = lazy[p]; // time to update this
lazy[p] = -1; // erase lazy flag
}
}
int RMQ(int p, int L, int R, int i, int j) { // O(log n)
propagate(p, L, R); // lazy propagation
if (i > j) return -1; // infeasible
if ((L >= i) && (R <= j)) return st[p]; // found the segment
int m = (L+R)/2;
return conquer(RMQ(l(p), L , m, i , min(m, j)),
RMQ(r(p), m+1, R, max(i, m+1), j ));
}
// notice range updates
void update(int p, int L, int R, int i, int j, int val) { // O(log n)
propagate(p, L, R); // lazy propagation
if (i > j) return;
if ((L >= i) && (R <= j)) { // found the segment
lazy[p] = val; // update this
propagate(p, L, R); // lazy propagation
}
else {
int m = (L+R)/2;
update(l(p), L , m, i , min(m, j), val);
update(r(p), m+1, R, max(i, m+1), j , val);
int lsubtree = (lazy[l(p)] != -1) ? lazy[l(p)] : st[l(p)];
int rsubtree = (lazy[r(p)] != -1) ? lazy[r(p)] : st[r(p)];
st[p] = (lsubtree <= rsubtree) ? st[l(p)] : st[r(p)];
}
}
public:
SegmentTree(int sz) : n(sz), st(4*n), lazy(4*n, -1) {}
SegmentTree(const vi &initialA) : SegmentTree((int)initialA.size()) {
A = initialA;
build(1, 0, n-1);
}
void update(int i, int j, int val) { update(1, 0, n-1, i, j, val); }
int RMQ(int i, int j) { return RMQ(1, 0, n-1, i, j); }
};