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).
Proof: optimal block size
If an operation costs for whole blocks plus for partial ones, the sum is minimised at (AM-GM: , with equality iff ). With operations of cost and of cost , the total is minimised at , giving . Tuning when the two sides differ is often a 2-3x speedup — rather than trust the formula’s constants exactly, measure: try and keep the fastest.
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
| Objects | Heavy () | Light () |
|---|---|---|
| values by frequency | at most of them: precompute an answer per heavy value | brute force over their occurrences |
| vertices by degree | at most : maintain an aggregate at each heavy vertex | iterate over their neighbours |
| patterns by length | at most distinct long ones: process each separately | short ones: group by length or trie of depth |
| query ranges by length | answer via precomputation | brute 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.)
Proof: out-degrees are and the total is
If then . Suppose has out-degree . All out-neighbours have degree , so they account for at least edge endpoints, hence and . The double loop costs . Each triangle is counted once: orient it acyclically (the orientation is by a total order), and it is found exactly from its lowest vertex.
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++;Proof: per query
The middle blocks’ mode is a valid starting candidate, and the true mode either is it or occurs in a partial block. For a value occurring in the left partial block, scanning from its leftmost in-range occurrence and testing the -th occurrence counts exactly its frequency in once it exceeds ; occurrences only in the right partial block are handled symmetrically from the right. Each loop step either increases or moves to the next position. can grow by at most the number of partial-block elements () beyond the middle mode, and there are at most positions, so the total is .
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”).