Patterns/Part VIII - Cross-Topic Deep Dives/Mo's Algorithm

Pattern Reference

Mo's Algorithm

"Offline query processing with sqrt decomposition. Sort queries by block, maintain pointer movement. Range queries."

Loading...

Deep Dive Tutorial

Mo's algorithm works when: (1) queries are offline (all known beforehand), (2) you can maintain an answer incrementally as the window expands/contracts by one element. The key insight: sort queries so the total movement of L and R pointers is minimized. With block size √n, R moves O(n) per block (O(n√n) total), and L moves O(√n) per query (O(q√n) total). Total: O((n+q)√n).

Mo's algorithm template
function moAlgorithm(n, queries, add, remove, getAnswer) {
    // queries: [{l, r, idx}]
    const block = Math.ceil(Math.sqrt(n));

    // Sort: by block of l, then by r (alternating for cache efficiency)
    queries.sort((a, b) => {
        const ba = Math.floor(a.l / block), bb = Math.floor(b.l / block);
        if (ba !== bb) return ba - bb;
        return ba % 2 === 0 ? a.r - b.r : b.r - a.r; // alternating
    });

    const answers = new Array(queries.length);
    let curL = 0, curR = -1;

    for (const {l, r, idx} of queries) {
        // Expand/contract to [l, r]
        while (curR < r) add(++curR);
        while (curL > l) add(--curL);
        while (curR > r) remove(curR--);
        while (curL < l) remove(curL++);
        answers[idx] = getAnswer();
    }
    return answers;
}

// Example: count distinct elements in range [l, r]
// add(i): freq[arr[i]]++; if freq[arr[i]] === 1: distinctCount++
// remove(i): freq[arr[i]]--; if freq[arr[i]] === 0: distinctCount--
// getAnswer(): return distinctCount

Worked Problems

square-dashed
Mo's algorithm conditions:
- Offline: all queries known upfront
- Incremental: can add/remove one element and update answer in O(1) or O(log n)
- Works for: distinct count, sum, XOR, frequency queries

Block size selection:
- Typical: √n ≈ 300-350 for n = 100,000
- Optimal: n/√q if q queries
- Alternating sort (even blocks right→left, odd blocks left→right) reduces constant by ~2x

Mo's on trees: Euler tour the tree to flatten, then apply Mo's on the flattened array. Handles path queries offline.

Time complexity: O((n + q) · √n). For n = q = 10^5: ~3 × 10^7 operations — fits in 2-3 seconds.