Store the array as maximal runs of equal values in an ordered map. Range assign collapses many runs into one. Any range operation then visits the runs it covers. It’s also called ODT (“old driver tree”) after CF 896C.

Implementation

template<class T> struct IntervalMap {
    map<ll, T> m;                                  // run start -> value; key hi is a sentinel
    IntervalMap(ll lo, ll hi, T init) { m[lo] = init; m[hi] = init; }
    auto split(ll x) {                             // make x the start of a run
        auto it = prev(m.upper_bound(x));
        return it->first == x ? it : m.emplace_hint(next(it), x, it->second);
    }
    void assign(ll l, ll r, T v) {                 // a[l, r) = v
        auto ir = split(r), il = split(l);
        m.erase(next(il), ir); il->second = v;
    }
    template<class F> void each(ll l, ll r, F f) { // f(start, end, value&) for every run in [l, r)
        auto ir = split(r), il = split(l);
        for (auto it = il; it != ir; ++it) f(it->first, next(it)->first, it->second);
    }
};

Split before . With this map + emplace_hint version either order works, because splitting never erases a node. In the common set<Node> implementation, splitting re-inserts a run, so splitting first can invalidate the iterator for the run containing .

Complexity

Each assign removes all runs inside it and adds at most 3, so the number of runs created overall is , and assign is amortized. The cost of each is proportional to the number of runs visited.

  • With random operations where a constant fraction are assigns (CF 896C), the expected number of runs stays -ish and every operation is fast.
  • Adversarial tests with few assigns make each per call. Then you need a real structure (lazy segment tree, segment tree beats).

Where it is the intended or best tool

  • Range assign + anything weird on the runs: -th smallest in a range (collect runs, sort), sum of -th powers mod in a range (per run: pow(value, x) * length). These are the CF 896C operations.
  • Union of intervals / painting: IntervalMap<bool>; total painted length via each.
  • “Colour the range with , report how many cells change colour”: count lengths of runs whose value differs from before assigning. Amortized deterministically, because each assign removes the runs it counts.
  • Maintaining a set of disjoint intervals (merge on insert, split on delete), like “free memory blocks”, “covered positions”, or “segments of equal values with counts”.
  • Amortized arguments on trees: the same idea along HLD chains (assign a colour to a root path, count colour changes) gives the classic amortized “access” analysis used in link-cut tree style problems.

A set<pair<ll,ll>> of disjoint intervals with the same split/merge discipline is the minimal version when there’s no value to store.