Split the array into blocks of size . Queries touch whole blocks plus individual elements, giving per operation. Slower than a segment tree asymptotically, but it handles operations a segment tree cannot.
Basic range sum
int B; // block size ~ sqrt(n)
vector<long long> a, blockSum;
void build(int n) {
B = max(1, (int)sqrt(n));
blockSum.assign((n + B - 1) / B, 0);
for (int i = 0; i < n; i++) blockSum[i / B] += a[i];
}
void update(int i, long long v) { blockSum[i / B] += v - a[i]; a[i] = v; }
long long query(int l, int r) {
long long res = 0;
while (l <= r && l % B) res += a[l++]; // partial left block
while (l + B - 1 <= r) { res += blockSum[l / B]; l += B; } // whole blocks
while (l <= r) res += a[l++]; // partial right block
return res;
}Choosing the block size
balances “number of blocks” against “elements per block”. But when updates and queries have different costs, tune it:
For updates and queries on , is right. If updates are rare, use larger blocks.
Why bother, given segment trees exist
Sqrt decomposition wins in three situations:
1. The operation has no efficient merge. A segment tree needs an associative combine. Sqrt decomposition only needs “rebuild a block in ”, which is a far weaker requirement.
2. You need per-block auxiliary structures. Keeping each block sorted allows queries a segment tree cannot answer cheaply:
// count elements > x in [l, r]
long long count = 0;
// partial blocks: scan directly
// whole blocks: binary search in the sorted copy
count += sorted[b].end() - upper_bound(sorted[b].begin(), sorted[b].end(), x);per query, and it supports “range add” by keeping a per-block offset.
3. The problem is genuinely offline — see Mo’s algorithm, which is sqrt decomposition applied to the query order rather than the array.
The variants
| Variant | Idea | Complexity |
|---|---|---|
| Blocks over the array | the standard | |
| Blocks + sorted copies | order-statistics queries | |
| Blocks + lazy per-block tag | range assign / add | |
| Mo’s algorithm | sort the queries by block | |
| Mo on trees | Mo over the Euler tour | |
| Sqrt rebuilding | rebuild the structure every operations | amortized |
| Sqrt on values | split the value range, not the indices | problem-specific |
| Buffered updates | collect updates, then rebuild |
Sqrt rebuilding
A general escape hatch: keep a fast static structure plus a small buffer of recent updates. Answer a query as “static answer, corrected by the buffered updates”, and rebuild the static structure every updates. This turns many “impossible to update” structures into updatable ones at a cost — it works for convex hulls, sorted lists, and precomputed tables alike.
Sqrt decomposition on trees
Split the tree into connected blocks of size , or by depth into layers. Less common than HLD or centroid decomposition, but occasionally the only thing that fits an unusual query.
When is fast enough
With , — comfortable. At it is and too slow. The rule of thumb: sqrt techniques are for .
See also: Mo’s Algorithm · Segment Tree · Range Query Techniques