Offline range queries where the window can be extended and shrunk by one element in (or ): count distinct, pairs of equal values, sum of , mex with a value-sqrt, mode with a count-of-counts. Reorder the queries so that the total pointer movement is .

Classic Mo

template<class Add, class Rem, class Ans>
void mo(int n, const vector<pair<int, int>>& qs, Add add, Rem rem, Ans ans) {   // queries [l, r)
    int q = sz(qs), B = max(1, int(n / sqrt(max(q, 1))));
    vector<int> ord(q); iota(all(ord), 0);
    sort(all(ord), [&](int i, int j) {
        int bi = qs[i].first / B, bj = qs[j].first / B;
        if (bi != bj) return bi < bj;
        return bi & 1 ? qs[i].second > qs[j].second : qs[i].second < qs[j].second;
    });
    int L = 0, R = 0;
    for (int id : ord) {
        auto [l, r] = qs[id];
        while (L > l) add(--L);                 // grow first, then shrink:
        while (R < r) add(R++);                 // the window is never "negative"
        while (L < l) rem(L++);
        while (R > r) rem(--R);
        ans(id);
    }
}

The alternating direction of (“odd-even sort”) saves about a third of the movement. Hilbert order (sort queries by the index of on a Hilbert curve of side ) is another 20-40% in practice:

ll hilbert(int x, int y, int k = 20) {   // 2^k >= n
    ll d = 0;
    for (int s = 1 << (k - 1); s; s >>= 1) {
        int rx = (x & s) > 0, ry = (y & s) > 0;
        d += ll(s) * s * ((3 * rx) ^ ry);
        if (!ry) { if (rx) x = s - 1 - x, y = s - 1 - y; swap(x, y); }
    }
    return d;
}

When the add/remove cost is not : if add is (Fenwick for “count of values ”), the total is . Usually better: keep updates and queries via a value-sqrt structure (counts per value + counts per value block). Then the total is . Classic targets: Codeforces 940F “Machine Learning” (mex of frequency counts, with updates), and “count of distinct values in a range” under point updates.

Mo with updates

Queries become where = number of updates before the query. Sort by with . Moving applies/undoes one update: if the position is inside the window, remove the old value and add the new one; always write the array.

struct Upd { int pos, before, after; };            // fill "before" by simulating the array while reading
auto apply = [&](int k, bool fwd) {
    int p = ups[k].pos, v = fwd ? ups[k].after : ups[k].before;
    if (L <= p && p < R) { rem(p); a[p] = v; add(p); } else a[p] = v;
};
// per query: while (T < q.t) apply(T++, 1); while (T > q.t) apply(--T, 0); then move L and R as usual

Mo on trees (path queries)

Euler tour where each vertex appears twice: at entry () and exit (), array length . For a path with and :

  • if , the range is ;
  • otherwise the range is and must be added separately (toggle it before reading the answer and toggle it back).

Both add and rem become toggle(vertex): a vertex whose two occurrences both lie in the range cancels out.

Subtree queries need no trick: a subtree is the contiguous range of the usual single-occurrence Euler tour, and plain array Mo’s applies with no toggling. They’re usually better done by small-to-large (DSU on Tree) or a Fenwick tree over that same Euler-tour range; reach for Mo’s only when the aggregate resists both.

SituationMethodCost
Aggregate is associative, no updatesbinary lifting
Aggregate is associative, with updatesHLD + segment tree
Aggregate needs add/remove semanticsMo’s on trees
Counting over all pathscentroid decomposition
Small vertex subsets per queryvirtual tree

Mo’s on trees is the fallback for aggregates like “number of distinct values” that HLD cannot merge — its niche is real but narrow. The Hilbert-curve speedup for the query order applies here too, same as array Mo’s.

Rollback Mo (add-only)

When removing is impossible but you can add and undo (max, mode with only increasing counts, DSU connectivity, linear basis, “max distance between equal values”): group by the block of , sort by . For each block, reset to empty with at the block end. For each query, grow (persistently within the block), snapshot, add on the left, answer, roll back. Queries entirely inside one block are brute-forced from empty.

template<class AL, class AR, class Snap, class Roll, class Reset, class Ans>
void moRollback(int n, const vector<pair<int, int>>& qs, AL addL, AR addR, Snap snap, Roll roll, Reset reset, Ans ans) {
    int q = sz(qs); if (!q) return;
    int B = max(1, int(n / sqrt(q)));
    vector<int> ord(q); iota(all(ord), 0);
    sort(all(ord), [&](int i, int j) { return pair(qs[i].first / B, qs[i].second) < pair(qs[j].first / B, qs[j].second); });
    for (int i = 0; i < q;) {
        int blk = qs[ord[i]].first / B, end = min(n, (blk + 1) * B), r = end;
        reset();
        for (; i < q && qs[ord[i]].first / B == blk; i++) {
            auto [l, rr] = qs[ord[i]];
            if (rr <= end) { snap(); for (int j = l; j < rr; j++) addR(j); }
            else { while (r < rr) addR(r++); snap(); for (int j = end - 1; j >= l; j--) addL(j); }
            ans(ord[i]); roll();
        }
    }
}

Implement rollback with a history stack of (cell, old value) pairs pushed in add; snap records the stack size and the scalar answer. For range connectivity (components using edges , CodeChef GERALD07), add merges in a DSU with union by size and no path compression, and rollback pops merges: .

Second offline Mo (Mo with prefix queries)

For answers of the form (range inversions, pairs with ) where moving a pointer by one is too expensive ( each makes ). Moving from to changes the answer by :

  • the first term is a prefix quantity, precomputed for every in one sweep;
  • the second term is “elements against the prefix ”, recorded as one task per pointer run (not per step) at prefix .

A final sweep inserts into a structure with insert and query, and answers each task by iterating its range. Total: queries + inserts, memory. The answers come out as differences, so take prefix sums along the processing order. Luogu P5047 (range inversions), P4887.

Other variants

VariantIdeaCost
two ranges (e.g. two Euler-tour subtrees, CF 1767F)four pointers, sort by blocks of then , block
online Mo (forced online, with point updates)store the structure for every pair of blocks; query = stored + loose elements; update touches structures: adds per query, structures per update, copies in memory
bitset Mowindow state is a bitset of values; answer = bitset operations (e.g. “can / “)
Mo on 2D (submatrices)pointers per side-ish, rarely good

Checklist

  • Block size , not , when .
  • Extend before shrinking; with half-open windows, start from .
  • Compress values; keep counts in plain arrays, not maps.
  • If the answer needs “max of counts”, maintain count-of-counts (removal never needs a search because the max drops by at most one), or use rollback Mo.
  • Prefer an offline sweep when the query is decomposable (distinct values in a range: sweep , keep only the last occurrence in a Fenwick).