Some updates can’t be applied to a whole node in : chmin needs to know how many elements exceed , and mod x changes every element differently. Segment tree beats (Ji Ruyi, 2016) stores just enough to handle the easy cases in and recurses in the hard ones, with a potential argument bounding the total recursion. Historic-value trees track “the maximum each element ever had” or “the sum over time” with tags that remember the best prefix of the update sequence.

Chmin, chmax, add with sum, max and min

Each node stores max, strict second max, count of max (and the same for min) plus the sum. chmin(x) on a node:

  • : nothing to do;
  • : only the maximum elements change: sum -= (max1 - x) * cnt, max1 = x, tag it;
  • : recurse into the children.
struct Beats {
    static constexpr ll INF = LLONG_MAX / 4;
    struct N { ll mx1, mx2, mn1, mn2, cmx, cmn, sum, add; int len; };
    vector<N> t; int n;
    Beats(const vector<ll>& a) : n(sz(a)), t(4 * sz(a)) { build(1, 0, n, a); }
    void pull(int v) {
        N &x = t[v], &L = t[2 * v], &R = t[2 * v + 1];
        x.sum = L.sum + R.sum;
        if (L.mx1 == R.mx1) x.mx1 = L.mx1, x.cmx = L.cmx + R.cmx, x.mx2 = max(L.mx2, R.mx2);
        else if (L.mx1 > R.mx1) x.mx1 = L.mx1, x.cmx = L.cmx, x.mx2 = max(L.mx2, R.mx1);
        else x.mx1 = R.mx1, x.cmx = R.cmx, x.mx2 = max(L.mx1, R.mx2);
        if (L.mn1 == R.mn1) x.mn1 = L.mn1, x.cmn = L.cmn + R.cmn, x.mn2 = min(L.mn2, R.mn2);
        else if (L.mn1 < R.mn1) x.mn1 = L.mn1, x.cmn = L.cmn, x.mn2 = min(L.mn2, R.mn1);
        else x.mn1 = R.mn1, x.cmn = R.cmn, x.mn2 = min(L.mn1, R.mn2);
    }
    void build(int v, int l, int r, const vector<ll>& a) {
        t[v].len = r - l; t[v].add = 0;
        if (r - l == 1) { t[v] = {a[l], -INF, a[l], INF, 1, 1, a[l], 0, 1}; return; }
        int m = (l + r) / 2; build(2 * v, l, m, a); build(2 * v + 1, m, r, a); pull(v);
    }
    void applyAdd(int v, ll x) {
        N& a = t[v]; a.sum += x * a.len; a.mx1 += x; a.mn1 += x; a.add += x;
        if (a.mx2 != -INF) a.mx2 += x;
        if (a.mn2 != INF) a.mn2 += x;
    }
    void applyMin(int v, ll x) {                       // requires mx2 < x < mx1
        N& a = t[v]; if (a.mx1 <= x) return;
        a.sum -= (a.mx1 - x) * a.cmx;
        if (a.mn1 == a.mx1) a.mn1 = x;                 // node with one or two distinct values
        else if (a.mn2 == a.mx1) a.mn2 = x;
        a.mx1 = x;
    }
    void applyMax(int v, ll x) {                       // requires mn1 < x < mn2
        N& a = t[v]; if (a.mn1 >= x) return;
        a.sum += (x - a.mn1) * a.cmn;
        if (a.mx1 == a.mn1) a.mx1 = x;
        else if (a.mx2 == a.mn1) a.mx2 = x;
        a.mn1 = x;
    }
    void push(int v) {
        for (int c : {2 * v, 2 * v + 1}) {
            if (t[v].add) applyAdd(c, t[v].add);
            applyMin(c, t[v].mx1); applyMax(c, t[v].mn1);   // children never exceed the parent's extremes
        }
        t[v].add = 0;
    }
    void chmin(int v, int l, int r, int ql, int qr, ll x) {
        if (qr <= l || r <= ql || t[v].mx1 <= x) return;
        if (ql <= l && r <= qr && t[v].mx2 < x) { applyMin(v, x); return; }
        push(v); int m = (l + r) / 2;
        chmin(2 * v, l, m, ql, qr, x); chmin(2 * v + 1, m, r, ql, qr, x); pull(v);
    }
    void chmax(int v, int l, int r, int ql, int qr, ll x) {
        if (qr <= l || r <= ql || t[v].mn1 >= x) return;
        if (ql <= l && r <= qr && t[v].mn2 > x) { applyMax(v, x); return; }
        push(v); int m = (l + r) / 2;
        chmax(2 * v, l, m, ql, qr, x); chmax(2 * v + 1, m, r, ql, qr, x); pull(v);
    }
    void add(int v, int l, int r, int ql, int qr, ll x) {
        if (qr <= l || r <= ql) return;
        if (ql <= l && r <= qr) { applyAdd(v, x); return; }
        push(v); int m = (l + r) / 2;
        add(2 * v, l, m, ql, qr, x); add(2 * v + 1, m, r, ql, qr, x); pull(v);
    }
    ll sum(int v, int l, int r, int ql, int qr) {
        if (qr <= l || r <= ql) return 0;
        if (ql <= l && r <= qr) return t[v].sum;
        push(v); int m = (l + r) / 2;
        return sum(2 * v, l, m, ql, qr) + sum(2 * v + 1, m, r, ql, qr);
    }
};

Complexity: chmin/chmax with sum queries is amortized without adds. With range adds it’s . Potential: the number of distinct “max tags” across nodes; a recursion into a child happens only when two distinct values merge.

Test against a brute force with random interleavings; the two-or-fewer-distinct-values edge cases (mn1 == mx1, mn2 == mx1) are where bugs hide.

Other “recurse while it matters” updates

UpdateStop conditionBound
node max each change at least halves : changes per element
node max changes per element
with range addnode max min small: convert to add when per “distance” potential
divisible by the node’s lcm (capped)each change at least halves
/ every element agrees on the affected bits: then it’s an add per bit flip

Total cost is , plus sums of updates when combined with adds (count the potential carefully).

Historic maximum

Each element also tracks over time of . With range add only, a node’s tag is a pair (total add, best prefix of the adds since the last push). Applying a tag to a node: hmx = max(hmx, mx + h), mx += a, and the node’s own tag updates hadd = max(hadd, add + h), add += a.

With range assign too, a node that has been assigned since the last push only shifts its assigned value: tags become (add part, best add prefix) before the first assignment and (assigned value, best assigned value) after it. Push applies the add part first, then the assignment part. That’s Luogu P4314 (“CPU monitoring”).

Historic sum (sum over time of , queried over ranges): keep the sum of and of the historic sums, plus a “time” tag: each tick adds the current sum to the historic sum. Lazy tags are affine in (value, history) and compose like matrices . This is the engine of offline “number of subarrays within with property” problems: sweep the right end, maintain for each left end a 0/1 or value, and accumulate history to count over all right ends.

Historic minimum, with chmin/chmax: combine beats with “best prefix” tags per class (max elements and others).