Segment Tree Techniques: From Basic Templates to Advanced Competitive Programming Problems

Basic Segment Tree with Lazy Propagation

The fundamental segment tree template maintains range sum with lazy propagation for range addition operations.

#include <bits/stdc++.h>
using namespace std;
using int64 = long long;

struct SegNode {
    int left, right;
    int64 sum;
    int64 lazy;
};

class SegmentTree {
private:
    static const int MAXN = 100005;
    vector<SegNode> tree;
    vector<int64> arr;
    int size;
    
    int leftChild(int idx) { return idx << 1; }
    int rightChild(int idx) { return idx << 1 | 1; }
    
    void build(int node, int l, int r) {
        tree[node].left = l;
        tree[node].right = r;
        tree[node].lazy = 0;
        
        if (l == r) {
            tree[node].sum = arr[l];
            return;
        }
        
        int mid = (l + r) >> 1;
        build(leftChild(node), l, mid);
        build(rightChild(node), mid + 1, r);
        tree[node].sum = tree[leftChild(node)].sum + tree[rightChild(node)].sum;
    }
    
    void apply(int node, int64 value) {
        int len = tree[node].right - tree[node].left + 1;
        tree[node].sum += value * len;
        tree[node].lazy += value;
    }
    
    void pushDown(int node) {
        if (tree[node].lazy != 0) {
            apply(leftChild(node), tree[node].lazy);
            apply(rightChild(node), tree[node].lazy);
            tree[node].lazy = 0;
        }
    }
    
    void rangeAdd(int node, int l, int r, int64 value) {
        if (l <= tree[node].left && tree[node].right <= r) {
            apply(node, value);
            return;
        }
        
        pushDown(node);
        int mid = (tree[node].left + tree[node].right) >> 1;
        
        if (l <= mid) rangeAdd(leftChild(node), l, r, value);
        if (r > mid) rangeAdd(rightChild(node), l, r, value);
        
        tree[node].sum = tree[leftChild(node)].sum + tree[rightChild(node)].sum;
    }
    
    int64 rangeQuery(int node, int l, int r) {
        if (l <= tree[node].left && tree[node].right <= r) {
            return tree[node].sum;
        }
        
        pushDown(node);
        int mid = (tree[node].left + tree[node].right) >> 1;
        int64 result = 0;
        
        if (l <= mid) result += rangeQuery(leftChild(node), l, r);
        if (r > mid) result += rangeQuery(rightChild(node), l, r);
        
        return result;
    }
    
public:
    SegmentTree(int n) : size(n) {
        tree.resize(4 * n + 5);
        arr.resize(n + 1);
    }
    
    void initialize(const vector<int64>& data) {
        arr = data;
        build(1, 1, size);
    }
    
    void add(int l, int r, int64 value) {
        rangeAdd(1, l, r, value);
    }
    
    int64 query(int l, int r) {
        return rangeQuery(1, l, r);
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n, q;
    cin >> n >> q;
    
    vector<int64> input(n + 1);
    for (int i = 1; i <= n; i++) cin >> input[i];
    
    SegmentTree st(n);
    st.initialize(input);
    
    while (q--) {
        int operation;
        cin >> operation;
        if (operation == 1) {
            int x, y;
            int64 k;
            cin >> x >> y >> k;
            st.add(x, y, k);
        } else {
            int x, y;
            cin >> x >> y;
            cout << st.query(x, y) << '\n';
        }
    }
    
    return 0;
}

Segment Tree with Range Multiplication and Addition

This extended version supports both range multiplication and addition operations with lazy propagation.

#include <bits/stdc++.h>
using namespace std;
using int64 = long long;

struct Node {
    int l, r;
    int64 sum;
    int64 addTag;
    int64 mulTag;
};

class AdvancedSegmentTree {
private:
    vector<Node> tree;
    int n;
    int64 modulus;
    
    int lc(int idx) { return idx << 1; }
    int rc(int idx) { return idx << 1 | 1; }
    
    void pushUp(int idx) {
        tree[idx].sum = (tree[lc(idx)].sum + tree[rc(idx)].sum) % modulus;
    }
    
    void applyMul(int idx, int64 mulVal) {
        tree[idx].sum = (tree[idx].sum * mulVal) % modulus;
        tree[idx].mulTag = (tree[idx].mulTag * mulVal) % modulus;
        tree[idx].addTag = (tree[idx].addTag * mulVal) % modulus;
    }
    
    void applyAdd(int idx, int64 addVal) {
        int len = tree[idx].r - tree[idx].l + 1;
        tree[idx].sum = (tree[idx].sum + addVal * len) % modulus;
        tree[idx].addTag = (tree[idx].addTag + addVal) % modulus;
    }
    
    void pushDown(int idx) {
        int64 mulVal = tree[idx].mulTag;
        int64 addVal = tree[idx].addTag;
        
        if (mulVal != 1) {
            applyMul(lc(idx), mulVal);
            applyMul(rc(idx), mulVal);
            tree[idx].mulTag = 1;
        }
        
        if (addVal != 0) {
            applyAdd(lc(idx), addVal);
            applyAdd(rc(idx), addVal);
            tree[idx].addTag = 0;
        }
    }
    
    void build(int idx, int l, int r, const vector<int64>& arr) {
        tree[idx].l = l;
        tree[idx].r = r;
        tree[idx].addTag = 0;
        tree[idx].mulTag = 1;
        
        if (l == r) {
            tree[idx].sum = arr[l] % modulus;
            return;
        }
        
        int mid = (l + r) >> 1;
        build(lc(idx), l, mid, arr);
        build(rc(idx), mid + 1, r, arr);
        pushUp(idx);
    }
    
    void updateMul(int idx, int l, int r, int64 val) {
        if (l <= tree[idx].l && tree[idx].r <= r) {
            applyMul(idx, val);
            return;
        }
        pushDown(idx);
        int mid = (tree[idx].l + tree[idx].r) >> 1;
        if (l <= mid) updateMul(lc(idx), l, r, val);
        if (r > mid) updateMul(rc(idx), l, r, val);
        pushUp(idx);
    }
    
    void updateAdd(int idx, int l, int r, int64 val) {
        if (l <= tree[idx].l && tree[idx].r <= r) {
            applyAdd(idx, val);
            return;
        }
        pushDown(idx);
        int mid = (tree[idx].l + tree[idx].r) >> 1;
        if (l <= mid) updateAdd(lc(idx), l, r, val);
        if (r > mid) updateAdd(rc(idx), l, r, val);
        pushUp(idx);
    }
    
    int64 query(int idx, int l, int r) {
        if (l <= tree[idx].l && tree[idx].r <= r) {
            return tree[idx].sum;
        }
        pushDown(idx);
        int mid = (tree[idx].l + tree[idx].r) >> 1;
        int64 res = 0;
        if (l <= mid) res = (res + query(lc(idx), l, r)) % modulus;
        if (r > mid) res = (res + query(rc(idx), l, r)) % modulus;
        return res;
    }
    
public:
    void initialize(int size, int64 mod, const vector<int64>& arr) {
        n = size;
        modulus = mod;
        tree.resize(4 * n + 5);
        build(1, 1, n, arr);
    }
    
    void multiply(int l, int r, int64 val) {
        updateMul(1, l, r, val);
    }
    
    void add(int l, int r, int64 val) {
        updateAdd(1, l, r, val);
    }
    
    int64 queryRange(int l, int r) {
        return query(1, l, r);
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n, q;
    int64 mod;
    cin >> n >> q >> mod;
    
    vector<int64> data(n + 1);
    for (int i = 1; i <= n; i++) cin >> data[i];
    
    AdvancedSegmentTree ast;
    ast.initialize(n, mod, data);
    
    while (q--) {
        int op;
        cin >> op;
        if (op == 1) {
            int x, y;
            int64 k;
            cin >> x >> y >> k;
            ast.multiply(x, y, k);
        } else if (op == 2) {
            int x, y;
            int64 k;
            cin >> x >> y >> k;
            ast.add(x, y, k);
        } else {
            int x, y;
            cin >> x >> y;
            cout << ast.queryRange(x, y) << '\n';
        }
    }
    
    return 0;
}

Maintaining Range Maximum with Lazy Propagation

This variant maintains range maximum values enstead of sums, useful for problems involving height constraints or capacity limits.

#include <bits/stdc++.h>
using namespace std;

struct MaxNode {
    int l, r;
    int maximum;
    int lazy;
};

class MaxSegmentTree {
private:
    vector<MaxNode> tree;
    
    int leftChild(int idx) { return idx << 1; }
    int rightChild(int idx) { return idx << 1 | 1; }
    
    void pushUp(int idx) {
        tree[idx].maximum = max(tree[leftChild(idx)].maximum, 
                               tree[rightChild(idx)].maximum);
    }
    
    void pushDown(int idx) {
        if (tree[idx].lazy != 0) {
            tree[leftChild(idx)].maximum -= tree[idx].lazy;
            tree[rightChild(idx)].maximum -= tree[idx].lazy;
            tree[leftChild(idx)].lazy += tree[idx].lazy;
            tree[rightChild(idx)].lazy += tree[idx].lazy;
            tree[idx].lazy = 0;
        }
    }
    
    void build(int idx, int l, int r, const vector<int>& arr) {
        tree[idx].l = l;
        tree[idx].r = r;
        tree[idx].lazy = 0;
        
        if (l == r) {
            tree[idx].maximum = arr[l];
            return;
        }
        
        int mid = (l + r) >> 1;
        build(leftChild(idx), l, mid, arr);
        build(rightChild(idx), mid + 1, r, arr);
        pushUp(idx);
    }
    
    void rangeDec(int idx, int l, int r, int value) {
        if (l <= tree[idx].l && tree[idx].r <= r) {
            tree[idx].maximum -= value;
            tree[idx].lazy += value;
            return;
        }
        
        pushDown(idx);
        int mid = (tree[idx].l + tree[idx].r) >> 1;
        if (l <= mid) rangeDec(leftChild(idx), l, r, value);
        if (r > mid) rangeDec(rightChild(idx), l, r, value);
        pushUp(idx);
    }
    
    int rangeMin(int idx, int l, int r) {
        if (l <= tree[idx].l && tree[idx].r <= r) {
            return tree[idx].maximum;
        }
        pushDown(idx);
        int mid = (tree[idx].l + tree[idx].r) >> 1;
        int result = INT_MAX;
        if (l <= mid) result = min(result, rangeMin(leftChild(idx), l, r));
        if (r > mid) result = min(result, rangeMin(rightChild(idx), l, r));
        return result;
    }
    
public:
    void buildTree(int size, const vector<int>& arr) {
        tree.resize(4 * size + 5);
        build(1, 1, size, arr);
    }
    
    bool decrement(int l, int r, int value) {
        if (rangeMin(1, l, r) >= value) {
            rangeDec(1, l, r, value);
            return true;
        }
        return false;
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;
    vector<int> arr(n + 1);
    for (int i = 1; i <= n; i++) cin >> arr[i];
    
    MaxSegmentTree mst;
    mst.buildTree(n, arr);
    
    for (int i = 1; i <= m; i++) {
        int need, l, r;
        cin >> need >> l >> r;
        if (!mst.decrement(l, r, need)) {
            cout << -1 << '\n' << i;
            return 0;
        }
    }
    cout << 0;
    return 0;
}

Segment Tree for Maximum Subarray Sum

This implementation maintains maximum subarray sum using a merge function that combines prefix, sufffix, and maximum values.

#include <bits/stdc++.h>
using namespace std;

struct SubarrayNode {
    int l, r;
    int64 prefix;
    int64 suffix;
    int64 maximum;
    int64 total;
};

class MaxSubarrayTree {
private:
    vector<SubarrayNode> tree;
    
    int leftChild(int idx) { return idx << 1; }
    int rightChild(int idx) { return idx << 1 | 1; }
    
    SubarrayNode mergeNodes(const SubarrayNode& left, const SubarrayNode& right) {
        SubarrayNode result;
        result.l = left.l;
        result.r = right.r;
        result.total = left.total + right.total;
        
        result.prefix = max(left.prefix, left.total + right.prefix);
        result.suffix = max(right.suffix, right.total + left.suffix);
        result.maximum = max({left.maximum, right.maximum, left.suffix + right.prefix});
        
        return result;
    }
    
    void build(int idx, int l, int r, const vector<int64>& arr) {
        tree[idx].l = l;
        tree[idx].r = r;
        
        if (l == r) {
            tree[idx].prefix = tree[idx].suffix = tree[idx].maximum = tree[idx].total = arr[l];
            return;
        }
        
        int mid = (l + r) >> 1;
        build(leftChild(idx), l, mid, arr);
        build(rightChild(idx), mid + 1, r, arr);
        tree[idx] = mergeNodes(tree[leftChild(idx)], tree[rightChild(idx)]);
    }
    
    void pointUpdate(int idx, int pos, int64 value) {
        if (tree[idx].l == tree[idx].r) {
            tree[idx].prefix = tree[idx].suffix = tree[idx].maximum = tree[idx].total = value;
            return;
        }
        
        int mid = (tree[idx].l + tree[idx].r) >> 1;
        if (pos <= mid) pointUpdate(leftChild(idx), pos, value);
        else pointUpdate(rightChild(idx), pos, value);
        
        tree[idx] = mergeNodes(tree[leftChild(idx)], tree[rightChild(idx)]);
    }
    
    SubarrayNode query(int idx, int l, int r) {
        if (l <= tree[idx].l && tree[idx].r <= r) {
            return tree[idx];
        }
        
        int mid = (tree[idx].l + tree[idx].r) >> 1;
        if (r <= mid) return query(leftChild(idx), l, r);
        if (l > mid) return query(rightChild(idx), l, r);
        
        SubarrayNode leftResult = query(leftChild(idx), l, r);
        SubarrayNode rightResult = query(rightChild(idx), l, r);
        return mergeNodes(leftResult, rightResult);
    }
    
public:
    void initialize(int size, const vector<int64>& arr) {
        tree.resize(4 * size + 5);
        build(1, 1, size, arr);
    }
    
    void update(int position, int64 value) {
        pointUpdate(1, position, value);
    }
    
    int64 getMaxSubarraySum(int l, int r) {
        return query(1, l, r).maximum;
    }
};

Segment Tree for Binary String Operations

This specialized segment tree handles binary strings with range flip operations while maintaining longest consecutive sequences of 0s and 1s.

#include <bits/stdc++.h>
using namespace std;

struct BinaryNode {
    int l, r;
    int leftZero, leftOne;
    int rightZero, rightOne;
    int maxZero, maxOne;
    int flipTag;
};

class BinaryStringTree {
private:
    vector<BinaryNode> tree;
    string data;
    
    int lc(int idx) { return idx << 1; }
    int rc(int idx) { return idx << 1 | 1; }
    
    BinaryNode merge(const BinaryNode& a, const BinaryNode& b) {
        BinaryNode res;
        res.l = a.l;
        res.r = b.r;
        res.flipTag = 0;
        
        res.maxZero = max({a.maxZero, b.maxZero, a.rightZero + b.leftZero});
        res.maxOne = max({a.maxOne, b.maxOne, a.rightOne + b.leftOne});
        
        int leftLen = a.r - a.l + 1;
        res.leftZero = (a.leftZero == leftLen) ? leftLen + b.leftZero : a.leftZero;
        res.leftOne = (a.leftOne == leftLen) ? leftLen + b.leftOne : a.leftOne;
        
        int rightLen = b.r - b.l + 1;
        res.rightZero = (b.rightZero == rightLen) ? rightLen + a.rightZero : b.rightZero;
        res.rightOne = (b.rightOne == rightLen) ? rightLen + a.rightOne : b.rightOne;
        
        return res;
    }
    
    void applyFlip(int idx) {
        swap(tree[idx].leftZero, tree[idx].leftOne);
        swap(tree[idx].rightZero, tree[idx].rightOne);
        swap(tree[idx].maxZero, tree[idx].maxOne);
        tree[idx].flipTag ^= 1;
    }
    
    void pushDown(int idx) {
        if (tree[idx].flipTag & 1) {
            applyFlip(lc(idx));
            applyFlip(rc(idx));
            tree[idx].flipTag = 0;
        }
    }
    
    void build(int idx, int l, int r) {
        tree[idx].l = l;
        tree[idx].r = r;
        tree[idx].flipTag = 0;
        
        if (l == r) {
            if (data[l - 1] == '0') {
                tree[idx].leftZero = tree[idx].rightZero = tree[idx].maxZero = 1;
                tree[idx].leftOne = tree[idx].rightOne = tree[idx].maxOne = 0;
            } else {
                tree[idx].leftOne = tree[idx].rightOne = tree[idx].maxOne = 1;
                tree[idx].leftZero = tree[idx].rightZero = tree[idx].maxZero = 0;
            }
            return;
        }
        
        int mid = (l + r) >> 1;
        build(lc(idx), l, mid);
        build(rc(idx), mid + 1, r);
        tree[idx] = merge(tree[lc(idx)], tree[rc(idx)]);
    }
    
    void rangeFlip(int idx, int l, int r) {
        if (l <= tree[idx].l && tree[idx].r <= r) {
            applyFlip(idx);
            return;
        }
        pushDown(idx);
        int mid = (tree[idx].l + tree[idx].r) >> 1;
        if (l <= mid) rangeFlip(lc(idx), l, r);
        if (r > mid) rangeFlip(rc(idx), l, r);
        tree[idx] = merge(tree[lc(idx)], tree[rc(idx)]);
    }
    
    BinaryNode query(int idx, int l, int r) {
        if (l <= tree[idx].l && tree[idx].r <= r) {
            return tree[idx];
        }
        pushDown(idx);
        int mid = (tree[idx].l + tree[idx].r) >> 1;
        if (r <= mid) return query(lc(idx), l, r);
        if (l > mid) return query(rc(idx), l, r);
        return merge(query(lc(idx), l, r), query(rc(idx), l, r));
    }
    
public:
    void initialize(const string& s) {
        data = s;
        tree.resize(4 * s.length() + 5);
        build(1, 1, s.length());
    }
    
    void flip(int l, int r) {
        rangeFlip(1, l, r);
    }
    
    int queryMaxOnes(int l, int r) {
        return query(1, l, r).maxOne;
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n, q;
    cin >> n >> q;
    string s;
    cin >> s;
    
    BinaryStringTree bst;
    bst.initialize(s);
    
    while (q--) {
        int op, l, r;
        cin >> op >> l >> r;
        if (op == 1) {
            bst.flip(l, r);
        } else {
            cout << bst.queryMaxOnes(l, r) << '\n';
        }
    }
    
    return 0;
}

Segment Tree with Binary Indexed Tree Hybrid

For problems requiring offline query processing, a combination of segment tree operations with sorting by right boundary provides efficient solutions.

#include <bits/stdc++.h>
using namespace std;

struct Query {
    int left, right;
    int index;
    
    bool operator<(const Query& other) const {
        return right < other.right;
    }
};

class OfflineQuerySolver {
private:
    static const int MAXVAL = 1000006;
    vector<int> bit;
    vector<int> lastPos;
    vector<int> answer;
    int n;
    
    int lowBit(int x) { return x & (-x); }
    
    void bitUpdate(int pos, int delta) {
        for (int i = pos; i <= n; i += lowBit(i)) {
            bit[i] += delta;
        }
    }
    
    int bitQuery(int pos) {
        int sum = 0;
        for (int i = pos; i > 0; i -= lowBit(i)) {
            sum += bit[i];
        }
        return sum;
    }
    
public:
    void solve(const vector<int>& arr, const vector<Query>& queries) {
        n = arr.size() - 1;
        bit.assign(n + 2, 0);
        lastPos.assign(MAXVAL, 0);
        answer.resize(queries.size() + 1);
        
        vector<Query> sortedQueries = queries;
        sort(sortedQueries.begin(), sortedQueries.end());
        
        int queryIdx = 0;
        for (int i = 1; i <= n; i++) {
            int val = arr[i];
            
            if (lastPos[val] != 0) {
                bitUpdate(lastPos[val], -1);
            }
            bitUpdate(i, 1);
            lastPos[val] = i;
            
            while (queryIdx < sortedQueries.size() && 
                   sortedQueries[queryIdx].right == i) {
                int l = sortedQueries[queryIdx].left;
                int r = sortedQueries[queryIdx].right;
                answer[sortedQueries[queryIdx].index] = 
                    bitQuery(r) - bitQuery(l - 1);
                queryIdx++;
            }
        }
    }
    
    int getAnswer(int idx) const {
        return answer[idx];
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n;
    cin >> n;
    vector<int> arr(n + 1);
    for (int i = 1; i <= n; i++) cin >> arr[i];
    
    int m;
    cin >> m;
    vector<Query> queries(m);
    for (int i = 0; i < m; i++) {
        cin >> queries[i].left >> queries[i].right;
        queries[i].index = i;
    }
    
    OfflineQuerySolver solver;
    solver.solve(arr, queries);
    
    for (int i = 0; i < m; i++) {
        cout << solver.getAnswer(i) << '\n';
    }
    
    return 0;
}

Segment Tree for Prefix Sum Queries

For range maximum subarray sum queries, maintaining prefix sums with segment tree provides an elegant solution.

#include <bits/stdc++.h>
using namespace std;

struct PrefixNode {
    int l, r;
    long long prefixMin;
    long long prefixMax;
};

class PrefixSegTree {
private:
    vector<PrefixNode> tree;
    vector<long long> prefix;
    
    int lc(int idx) { return idx << 1; }
    int rc(int idx) { return idx << 1 | 1; }
    
    void pushUp(int idx) {
        tree[idx].prefixMin = min(tree[lc(idx)].prefixMin, 
                                  tree[rc(idx)].prefixMin);
        tree[idx].prefixMax = max(tree[lc(idx)].prefixMax, 
                                  tree[rc(idx)].prefixMax);
    }
    
    void build(int idx, int l, int r) {
        tree[idx].l = l;
        tree[idx].r = r;
        
        if (l == r) {
            tree[idx].prefixMin = prefix[l];
            tree[idx].prefixMax = prefix[l];
            return;
        }
        
        int mid = (l + r) >> 1;
        build(lc(idx), l, mid);
        build(rc(idx), mid + 1, r);
        pushUp(idx);
    }
    
    long long queryMin(int idx, int l, int r) {
        if (l <= tree[idx].l && tree[idx].r <= r) {
            return tree[idx].prefixMin;
        }
        
        int mid = (tree[idx].l + tree[idx].r) >> 1;
        long long result = LLONG_MAX;
        if (l <= mid) result = min(result, queryMin(lc(idx), l, r));
        if (r > mid) result = min(result, queryMin(rc(idx), l, r));
        return result;
    }
    
    long long queryMax(int idx, int l, int r) {
        if (l <= tree[idx].l && tree[idx].r <= r) {
            return tree[idx].prefixMax;
        }
        
        int mid = (tree[idx].l + tree[idx].r) >> 1;
        long long result = LLONG_MIN;
        if (l <= mid) result = max(result, queryMax(lc(idx), l, r));
        if (r > mid) result = max(result, queryMax(rc(idx), l, r));
        return result;
    }
    
public:
    void buildFromArray(const vector<long long>& arr) {
        int n = arr.size() - 1;
        prefix.resize(n + 1);
        prefix[0] = 0;
        for (int i = 1; i <= n; i++) {
            prefix[i] = prefix[i - 1] + arr[i];
        }
        
        tree.resize(4 * n + 5);
        build(1, 0, n);
    }
    
    long long queryRangeMax(int l, int r) {
        return queryMax(1, l, r);
    }
    
    long long queryRangeMin(int l, int r) {
        return queryMin(1, l, r);
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n, q;
    cin >> n >> q;
    
    vector<long long> arr(n + 1);
    for (int i = 1; i <= n; i++) {
        cin >> arr[i];
    }
    
    PrefixSegTree pst;
    pst.buildFromArray(arr);
    
    while (q--) {
        int a, b, c, d;
        cin >> a >> b >> c >> d;
        long long result = pst.queryRangeMax(c, d) - 
                          pst.queryRangeMin(a - 1, b - 1);
        cout << result << '\n';
    }
    
    return 0;
}

Segment Tree for Monotonic Sequence Detection

This implementation maintains sequence state including whether sequences are strictly increasing, strictly decreasing, or unimodal.

#include <bits/stdc++.h>
using namespace std;

enum SequenceState {
    SINGLE = 0,
    EQUAL = 1,
    STRICT_INC = 2,
    STRICT_DEC = 3,
    UNIMODAL = 4,
    IRREGULAR = 5
};

struct MonotoneNode {
    int l, r;
    int leftVal, rightVal;
    SequenceState state;
    int addTag;
};

class MonotoneSegTree {
private:
    vector<MonotoneNode> tree;
    vector<int> arr;
    
    int lc(int idx) { return idx << 1; }
    int rc(int idx) { return idx << 1 | 1; }
    
    MonotoneNode combine(const MonotoneNode& left, const MonotoneNode& right) {
        MonotoneNode res;
        res.l = left.l;
        res.r = right.r;
        res.leftVal = left.leftVal;
        res.rightVal = right.rightVal;
        res.addTag = 0;
        res.state = IRREGULAR;
        
        if (left.state == IRREGULAR || right.state == IRREGULAR) {
            res.state = IRREGULAR;
        } else if (left.state == SINGLE && right.state == SINGLE) {
            if (left.rightVal > right.rightVal) res.state = STRICT_DEC;
            else if (left.rightVal == right.rightVal) res.state = EQUAL;
            else res.state = STRICT_INC;
        } else if (left.state == SINGLE) {
            if (right.state == EQUAL) {
                res.state = (left.rightVal == right.rightVal) ? EQUAL : IRREGULAR;
            } else if (right.state == STRICT_INC) {
                res.state = (left.rightVal < right.leftVal) ? STRICT_INC : IRREGULAR;
            } else if (right.state == STRICT_DEC) {
                if (left.rightVal == right.leftVal) res.state = IRREGULAR;
                else if (left.rightVal > right.leftVal) res.state = STRICT_DEC;
                else res.state = UNIMODAL;
            } else if (right.state == UNIMODAL) {
                res.state = (left.rightVal < right.leftVal) ? UNIMODAL : IRREGULAR;
            }
        } else if (right.state == SINGLE) {
            if (left.state == EQUAL) {
                res.state = (right.leftVal == left.rightVal) ? EQUAL : IRREGULAR;
            } else if (left.state == STRICT_INC) {
                res.state = (right.leftVal == left.rightVal) ? IRREGULAR : 
                            (right.leftVal > left.rightVal) ? STRICT_INC : UNIMODAL;
            } else if (left.state == STRICT_DEC) {
                res.state = (right.leftVal < left.rightVal) ? STRICT_DEC : IRREGULAR;
            } else if (left.state == UNIMODAL) {
                res.state = (right.leftVal < left.rightVal) ? UNIMODAL : IRREGULAR;
            }
        } else {
            if (left.state == EQUAL || right.state == EQUAL) {
                res.state = IRREGULAR;
            } else if (left.state == STRICT_INC) {
                if (right.state == STRICT_INC) {
                    res.state = (left.rightVal < right.leftVal) ? STRICT_INC : IRREGULAR;
                } else if (right.state == STRICT_DEC) {
                    res.state = (left.rightVal != right.leftVal) ? UNIMODAL : IRREGULAR;
                } else {
                    res.state = (left.rightVal < right.leftVal) ? UNIMODAL : IRREGULAR;
                }
            } else if (left.state == STRICT_DEC) {
                if (right.state == STRICT_DEC) {
                    res.state = (left.rightVal > right.leftVal) ? STRICT_DEC : IRREGULAR;
                } else if (right.state == UNIMODAL) {
                    res.state = IRREGULAR;
                } else {
                    res.state = (left.rightVal > right.leftVal) ? UNIMODAL : IRREGULAR;
                }
            } else if (left.state == UNIMODAL) {
                if (right.state == STRICT_DEC || right.state == UNIMODAL) {
                    res.state = IRREGULAR;
                } else {
                    res.state = (left.rightVal > right.leftVal) ? UNIMODAL : IRREGULAR;
                }
            }
        }
        
        return res;
    }
    
    void pushDown(int idx) {
        if (tree[idx].addTag != 0) {
            int tag = tree[idx].addTag;
            tree[lc(idx)].leftVal += tag;
            tree[lc(idx)].rightVal += tag;
            tree[lc(idx)].addTag += tag;
            tree[rc(idx)].leftVal += tag;
            tree[rc(idx)].rightVal += tag;
            tree[rc(idx)].addTag += tag;
            tree[idx].addTag = 0;
        }
    }
    
    void build(int idx, int l, int r) {
        tree[idx].l = l;
        tree[idx].r = r;
        tree[idx].addTag = 0;
        
        if (l == r) {
            tree[idx].leftVal = tree[idx].rightVal = arr[l];
            tree[idx].state = SINGLE;
            return;
        }
        
        int mid = (l + r) >> 1;
        build(lc(idx), l, mid);
        build(rc(idx), mid + 1, r);
        tree[idx] = combine(tree[lc(idx)], tree[rc(idx)]);
    }
    
    void rangeAdd(int idx, int l, int r, int value) {
        if (l <= tree[idx].l && tree[idx].r <= r) {
            tree[idx].leftVal += value;
            tree[idx].rightVal += value;
            tree[idx].addTag += value;
            return;
        }
        
        pushDown(idx);
        int mid = (tree[idx].l + tree[idx].r) >> 1;
        if (l <= mid) rangeAdd(lc(idx), l, r, value);
        if (r > mid) rangeAdd(rc(idx), l, r, value);
        tree[idx] = combine(tree[lc(idx)], tree[rc(idx)]);
    }
    
    MonotoneNode query(int idx, int l, int r) {
        if (l <= tree[idx].l && tree[idx].r <= r) {
            return tree[idx];
        }
        
        pushDown(idx);
        int mid = (tree[idx].l + tree[idx].r) >> 1;
        if (r <= mid) return query(lc(idx), l, r);
        if (l > mid) return query(rc(idx), l, r);
        return combine(query(lc(idx), l, r), query(rc(idx), l, r));
    }
    
public:
    void initialize(const vector<int>& input) {
        arr = input;
        int n = arr.size() - 1;
        tree.resize(4 * n + 5);
        build(1, 1, n);
    }
    
    void add(int l, int r, int value) {
        rangeAdd(1, l, r, value);
    }
    
    bool isSingleOrEqual(int l, int r) {
        MonotoneNode result = query(1, l, r);
        return result.state == SINGLE || result.state == EQUAL;
    }
    
    bool isSingleOrIncreasing(int l, int r) {
        MonotoneNode result = query(1, l, r);
        return result.state == SINGLE || result.state == STRICT_INC;
    }
    
    bool isSingleOrDecreasing(int l, int r) {
        MonotoneNode result = query(1, l, r);
        return result.state == SINGLE || result.state == STRICT_DEC;
    }
    
    bool isUnimodal(int l, int r) {
        MonotoneNode result = query(1, l, r);
        return result.state == UNIMODAL;
    }
};

Tags: segment tree Data Structures Lazy Propagation range queries Competitive Programming

Posted on Sat, 27 Jun 2026 16:07:29 +0000 by oshecho