Segment Tree Variants and Categorization Techniques

Linear Operation Segment Trees

The most basic form of segment tree handles linear operations that satisfy commutativity and associativity, such as addition. Since operations do not depend on each other, maintaining lazy propagation is straightforward. For single-point modifications, a Fenwick Tree (Binary Indexed Tree) is often a more efficient alternative.

Implementation

#define LEFT(u) (u * 2)
#define RIGHT(u) (u * 2 + 1)

struct TreeNode {
    long long sum;
    long long add_pending;
} tree[4 * MAX];

void construct(int node, int start, int end) {
    if (start == end) {
        tree[node].sum = arr[start];
        return;
    }
    int mid = (start + end) / 2;
    construct(LEFT(node), start, mid);
    construct(RIGHT(node), mid + 1, end);
    tree[node].sum = tree[LEFT(node)].sum + tree[RIGHT(node)].sum;
}

void apply_lazy(int node, int start, int end, long long value) {
    tree[node].sum += value * (end - start + 1);
    tree[node].add_pending += value;
}

void push_down(int node, int start, int end) {
    if (tree[node].add_pending == 0) return;
    
    int mid = (start + end) / 2;
    apply_lazy(LEFT(node), start, mid, tree[node].add_pending);
    apply_lazy(RIGHT(node), mid + 1, end, tree[node].add_pending);
    tree[node].add_pending = 0;
}

void range_update(int node, int start, int end, int l, int r, long long val) {
    if (l > end || r < start) return;
    if (l <= start && end <= r) {
        apply_lazy(node, start, end, val);
        return;
    }
    push_down(node, start, end);
    int mid = (start + end) / 2;
    range_update(LEFT(node), start, mid, l, r, val);
    range_update(RIGHT(node), mid + 1, end, l, r, val);
    tree[node].sum = tree[LEFT(node)].sum + tree[RIGHT(node)].sum;
}

long long range_query(int node, int start, int end, int l, int r) {
    if (l > end || r < start) return 0;
    if (l <= start && end <= r) return tree[node].sum;
    
    push_down(node, start, end);
    int mid = (start + end) / 2;
    return range_query(LEFT(node), start, mid, l, r) + 
           range_query(RIGHT(node), mid + 1, end, l, r);
}

Non-Linear Operations with Dependent Marks

This category handles operations where order matters, such as combining range addition and range multiplciation. You cannot simply swap these operations; for example, (x + a) * b differs from (x * b) + a. The standard approach is to enforce a fixed priority: multiplication over addition. When a multiplication mark is applied, it must also scale the existing addition mark to preserve correctness.

Implementation

struct OpNode {
    long long total;
    long long add_mark;
    long long mul_mark;
} seg[4 * MAX];

void build_tree(int idx, int lo, int hi) {
    seg[idx].add_mark = 0;
    seg[idx].mul_mark = 1;
    if (lo == hi) {
        seg[idx].total = arr[lo] % MOD;
        return;
    }
    int mid = (lo + hi) / 2;
    build_tree(LEFT(idx), lo, mid);
    build_tree(RIGHT(idx), mid + 1, hi);
    seg[idx].total = (seg[LEFT(idx)].total + seg[RIGHT(idx)].total) % MOD;
}

void propagate(int idx, int lo, int hi) {
    int mid = (lo + hi) / 2;
    int left_len = mid - lo + 1;
    int right_len = hi - mid;

    // Apply to left child
    seg[LEFT(idx)].total = (seg[LEFT(idx)].total * seg[idx].mul_mark + seg[idx].add_mark * left_len) % MOD;
    seg[LEFT(idx)].add_mark = (seg[LEFT(idx)].add_mark * seg[idx].mul_mark + seg[idx].add_mark) % MOD;
    seg[LEFT(idx)].mul_mark = (seg[LEFT(idx)].mul_mark * seg[idx].mul_mark) % MOD;

    // Apply to right child
    seg[RIGHT(idx)].total = (seg[RIGHT(idx)].total * seg[idx].mul_mark + seg[idx].add_mark * right_len) % MOD;
    seg[RIGHT(idx)].add_mark = (seg[RIGHT(idx)].add_mark * seg[idx].mul_mark + seg[idx].add_mark) % MOD;
    seg[RIGHT(idx)].mul_mark = (seg[RIGHT(idx)].mul_mark * seg[idx].mul_mark) % MOD;

    // Reset current node marks
    seg[idx].add_mark = 0;
    seg[idx].mul_mark = 1;
}

void multiply_update(int idx, int lo, int hi, int ul, int ur, long long k) {
    if (ul > hi || ur < lo) return;
    if (ul <= lo && hi <= ur) {
        seg[idx].total = (seg[idx].total * k) % MOD;
        seg[idx].add_mark = (seg[idx].add_mark * k) % MOD;
        seg[idx].mul_mark = (seg[idx].mul_mark * k) % MOD;
        return;
    }
    propagate(idx, lo, hi);
    int mid = (lo + hi) / 2;
    multiply_update(LEFT(idx), lo, mid, ul, ur, k);
    multiply_update(RIGHT(idx), mid + 1, hi, ul, ur, k);
    seg[idx].total = (seg[LEFT(idx)].total + seg[RIGHT(idx)].total) % MOD;
}

void addition_update(int idx, int lo, int hi, int al, int ar, long long k) {
    if (al > hi || ar < lo) return;
    if (al <= lo && hi <= ar) {
        seg[idx].total = (seg[idx].total + k * (hi - lo + 1)) % MOD;
        seg[idx].add_mark = (seg[idx].add_mark + k) % MOD;
        return;
    }
    propagate(idx, lo, hi);
    int mid = (lo + hi) / 2;
    addition_update(LEFT(idx), lo, mid, al, ar, k);
    addition_update(RIGHT(idx), mid + 1, hi, al, ar, k);
    seg[idx].total = (seg[LEFT(idx)].total + seg[RIGHT(idx)].total) % MOD;
}

Handling Assignment and Addition Conflicts

When mixing range assignment (cover) with range addition, assignment takes absolute priority. If an asignment occurs, any pending addition marks must be cleared. Conversely, if an addition occurs while an assignment mark exists, the addition should simply modify the assignment value rather than creating a separate addition mark.

Implementation

const long long NULL_VAL = -1;

struct CoverNode {
    long long max_val;
    long long cover_mark; // -1 means no cover
    long long add_mark;
} data[4 * MAX];

void init_node(int idx, int lo, int hi) {
    data[idx].cover_mark = NULL_VAL;
    data[idx].add_mark = 0;
    if (lo == hi) {
        data[idx].max_val = arr[lo];
        return;
    }
    int mid = (lo + hi) / 2;
    init_node(LEFT(idx), lo, mid);
    init_node(RIGHT(idx), mid + 1, hi);
    data[idx].max_val = max(data[LEFT(idx)].max_val, data[RIGHT(idx)].max_val);
}

void push_layer(int idx, int lo, int hi) {
    int mid = (lo + hi) / 2;
    
    if (data[idx].cover_mark != NULL_VAL) {
        // Assignment overrides everything
        data[LEFT(idx)].max_val = data[idx].cover_mark;
        data[LEFT(idx)].cover_mark = data[idx].cover_mark;
        data[LEFT(idx)].add_mark = 0;

        data[RIGHT(idx)].max_val = data[idx].cover_mark;
        data[RIGHT(idx)].cover_mark = data[idx].cover_mark;
        data[RIGHT(idx)].add_mark = 0;

        data[idx].cover_mark = NULL_VAL;
    } else if (data[idx].add_mark != 0) {
        // Apply addition
        data[LEFT(idx)].max_val += data[idx].add_mark;
        if (data[LEFT(idx)].cover_mark != NULL_VAL) {
            data[LEFT(idx)].cover_mark += data[idx].add_mark;
        } else {
            data[LEFT(idx)].add_mark += data[idx].add_mark;
        }

        data[RIGHT(idx)].max_val += data[idx].add_mark;
        if (data[RIGHT(idx)].cover_mark != NULL_VAL) {
            data[RIGHT(idx)].cover_mark += data[idx].add_mark;
        } else {
            data[RIGHT(idx)].add_mark += data[idx].add_mark;
        }

        data[idx].add_mark = 0;
    }
}

void cover_update(int idx, int lo, int hi, int cl, int cr, long long val) {
    if (cl > hi || cr < lo) return;
    if (cl <= lo && hi <= cr) {
        data[idx].max_val = val;
        data[idx].cover_mark = val;
        data[idx].add_mark = 0;
        return;
    }
    push_layer(idx, lo, hi);
    int mid = (lo + hi) / 2;
    cover_update(LEFT(idx), lo, mid, cl, cr, val);
    cover_update(RIGHT(idx), mid + 1, hi, cl, cr, val);
    data[idx].max_val = max(data[LEFT(idx)].max_val, data[RIGHT(idx)].max_val);
}

Complex Merging and Interval Logic

The third categoyr involves problems where merging two intervals is non-trivial. The result often depends on the "boundary" or the connection point between two segments. This requires storing auxiliary information like prefix/suffix states. A classic example is finding the longest alternating subsequence in a dynamic array.

Implementation

struct MergeNode {
    int left_char, right_char;
    int prefix_len, suffix_len, best_len;
} state[4 * MAX];

void merge_up(int idx, int lo, int hi) {
    int mid = (lo + hi) / 2;
    int len_left = mid - lo + 1;
    int len_right = hi - mid;

    MergeNode &cur = state[idx];
    MergeNode &l = state[LEFT(idx)];
    MergeNode &r = state[RIGHT(idx)];

    cur.left_char = l.left_char;
    cur.right_char = r.right_char;
    cur.best_len = max(l.best_len, r.best_len);
    cur.prefix_len = l.prefix_len;
    cur.suffix_len = r.suffix_len;

    if (l.right_char != r.left_char) {
        // Can connect
        cur.best_len = max(cur.best_len, l.suffix_len + r.prefix_len);
        
        if (l.prefix_len == len_left) {
            cur.prefix_len = len_left + r.prefix_len;
        }
        if (r.suffix_len == len_right) {
            cur.suffix_len = len_right + l.suffix_len;
        }
    }
}

void build_alt(int idx, int lo, int hi) {
    if (lo == hi) {
        state[idx].left_char = state[idx].right_char = arr[lo];
        state[idx].prefix_len = state[idx].suffix_len = state[idx].best_len = 1;
        return;
    }
    int mid = (lo + hi) / 2;
    build_alt(LEFT(idx), lo, mid);
    build_alt(RIGHT(idx), mid + 1, hi);
    merge_up(idx, lo, hi);
}

void point_modify(int idx, int lo, int hi, int pos) {
    if (lo == hi) {
        state[idx].left_char ^= 1;
        state[idx].right_char ^= 1;
        return;
    }
    int mid = (lo + hi) / 2;
    if (pos <= mid) point_modify(LEFT(idx), lo, mid, pos);
    else point_modify(RIGHT(idx), mid + 1, hi, pos);
    merge_up(idx, lo, hi);
}

Tags: segment-tree data-structures competitive-programming lazy-propagation interval-management

Posted on Sun, 28 Jun 2026 16:55:59 +0000 by sycoj0ker