Split something (the array, the values, the time line, the set of objects) into -sized pieces so that two different costs balance. It’s slower than a tree, but it only needs “rebuild a block in ” rather than an associative merge, so it handles operations nothing else can. Rule of thumb: fine for .

Blocks over the array

int B; vector<ll> a, blk;                              // blk[b] = aggregate of block b
void build() { B = max(1, (int)sqrt(sz(a))); blk.assign((sz(a) + B - 1) / B, 0); for (int i = 0; i < sz(a); i++) blk[i / B] += a[i]; }
void update(int i, ll v) { blk[i / B] += v - a[i]; a[i] = v; }
ll query(int l, int r) {                               // [l, r)
    ll s = 0;
    while (l < r && l % B) s += a[l++];
    while (l + B <= r) s += blk[l / B], l += B;
    while (l < r) s += a[l++];
    return s;
}

Per-block extras make it powerful:

  • lazy tag per block (add/assign applied to whole blocks, pushed when a partial block is touched);
  • sorted copy per block: count in , with range add as a per-block offset; rebuilding a partially updated block costs (or by merging);
  • frequency table per block prefix ( = occurrences of in blocks , memory) for “count of in a range” in when values are small;
  • convex hull per block for “add to range, query max of ” (rebuild the hull on partial updates; whole-block adds shift a query pointer).

per operation at is about — comfortable; at it’s and usually too slow, which is the concrete version of the "" rule of thumb above.

Sqrt rebuilding (buffered updates)

Keep a fast static structure plus a buffer of recent updates. Answer each query from the static structure and correct it by scanning the buffer. Rebuild every updates. Total: , minimised at . This makes sorted arrays, convex hulls, Aho-Corasick automata, sparse tables and precomputed DP tables “updatable”.

Heavy/light by a threshold

ObjectsHeavy ()Light ()
values by frequencyat most of them: precompute an answer per heavy valuebrute force over their occurrences
vertices by degreeat most : maintain an aggregate at each heavy vertexiterate over their neighbours
patterns by lengthat most distinct long ones: process each separatelyshort ones: group by length or trie of depth
query ranges by lengthanswer via precomputationbrute force
moduli in “add to every ”: touch the positions directly: keep and add it at query time

Triangle counting in : orient each edge from the lower endpoint to the higher one, then for each vertex mark its out-neighbours and scan their out-neighbours.

for (auto [u, v] : edges) (pair(deg[u], u) < pair(deg[v], v) ? g[u] : g[v]).push_back(u ^ v ^ (pair(deg[u], u) < pair(deg[v], v) ? u : v));
for (int u = 0; u < n; u++) {
    for (int v : g[u]) mark[v] = u + 1;
    for (int v : g[u]) for (int w : g[v]) cnt += mark[w] == u + 1;
}

(The first line just appends the higher endpoint to the lower endpoint’s list.)

Distinct weights bound: if positive integer weights sum to , there are at most distinct values (since forces ). So subset sum with total becomes , or with bitsets after binary splitting of multiplicities.

Range mode (online, static array)

Precompute the mode of every block interval in . For a query, start from the middle blocks’ mode and try to beat using elements of the two partial blocks, checking via each value’s sorted position list whether its -th occurrence from here is still in range.

// pos[v]: sorted positions of v; idx[i]: rank of i inside pos[a[i]]
// left partial block: for i from l upward:   while (idx[i] + c < sz(pos[a[i]]) && pos[a[i]][idx[i] + c] < r) best = a[i], c++;
// right partial block: for i from r-1 down:  while (idx[i] - c >= 0 && pos[a[i]][idx[i] - c] >= l) best = a[i], c++;

Sqrt tree

queries for any associative operation — including non-idempotent ones a sparse table can’t handle, like sum, product mod , matrix product or affine-map composition — with point updates and memory. Layers of blocks of size store prefix and suffix aggregates within blocks, plus “between” tables of block-interval aggregates. A query picks the layer where and lie in the same block but different sub-blocks (found from the highest differing bit, like the disjoint sparse table), and combines suffix + between + prefix. It’s rarely needed: a disjoint sparse table is simpler if there are no updates, and a segment tree is simpler if is fine.

Sqrt on time and on trees

  • Operation batching: process operations in groups of ; inside a group treat the structure as static plus a brute-force pass over the group’s updates; apply the group at its end. This is the “rebuilding” idea indexed by time.
  • Tree blocks: cluster the tree into connected blocks of size (DFS with a stack, cut when a subtree accumulates vertices). Mo on trees is usually easier (Mo’s Algorithm).
  • Sqrt on depths / on values: split by a threshold depth, or treat values and differently (for example “for each small modulus keep a table; for big moduli there are few multiples”).