Sqrt Decomposition
Sqrt Decomposition (also called Block Decomposition) is a technique for answering range queries and performing updates on an array in roughly time. It works by splitting the array into blocks of size approximately and precomputing an aggregate (sum, minimum, maximum, etc.) for each block. It is simpler to implement than a Segment Tree or Fenwick Tree and is a great stepping stone toward those structures.
Purpose
Sqrt Decomposition is useful when you need to:
- Answer range queries (sum, min, max, gcd, etc.) faster than the brute-force scan.
- Support point or range updates without the complexity of a Segment Tree.
- Trade a small amount of query speed for a much simpler implementation — useful in contests when time is short.
How It Works
- Divide the array of size into blocks of size .
- Precompute an aggregate value (e.g., sum) for every block in .
- Query(l, r):
- For any blocks that are fully contained within
[l, r], use the precomputed block aggregate directly — per block. - For the partial blocks at the two ends of the range, iterate element by element.
- Total blocks touched is at most , so a query runs in .
- For any blocks that are fully contained within
- Update(idx, val):
- Update the element directly, then update the aggregate of the block it belongs to — .
Operations
- Build: Construct block aggregates from the given array — .
- Query: Retrieve an aggregate over a range
[l, r]— . - Update: Modify a single element and refresh its block's aggregate — .
Time Complexity
- Building:
- Range Query:
- Point Update:
- Space:
Implementations
C++
#include <bits/stdc++.h>
using namespace std;
class SqrtDecomposition {
vector<int> arr;
vector<long long> blockSum;
int n, blockSize;
public:
SqrtDecomposition(vector<int>& input) {
arr = input;
n = arr.size();
blockSize = max(1, (int)sqrt(n));
blockSum.assign((n / blockSize) + 1, 0);
for (int i = 0; i < n; i++) {
blockSum[i / blockSize] += arr[i];
}
}
// Sum over [l, r], inclusive, 0-indexed
long long query(int l, int r) {
long long sum = 0;
while (l <= r) {
int startOfBlock = (l / blockSize) * blockSize;
int endOfBlock = min(startOfBlock + blockSize - 1, n - 1);
if (startOfBlock == l && endOfBlock <= r) {
// Fully contained block — use precomputed aggregate
sum += blockSum[l / blockSize];
l = endOfBlock + 1;
} else {
// Partial block — walk element by element
sum += arr[l];
l++;
}
}
return sum;
}
void update(int idx, int val) {
blockSum[idx / blockSize] += (val - arr[idx]);
arr[idx] = val;
}
};
Java
class SqrtDecomposition {
private int[] arr;
private long[] blockSum;
private int n, blockSize;
public SqrtDecomposition(int[] input) {
arr = input.clone();
n = arr.length;
blockSize = Math.max(1, (int) Math.sqrt(n));
blockSum = new long[(n / blockSize) + 1];
for (int i = 0; i < n; i++) {
blockSum[i / blockSize] += arr[i];
}
}
public long query(int l, int r) {
long sum = 0;
while (l <= r) {
int startOfBlock = (l / blockSize) * blockSize;
int endOfBlock = Math.min(startOfBlock + blockSize - 1, n - 1);
if (startOfBlock == l && endOfBlock <= r) {
sum += blockSum[l / blockSize];
l = endOfBlock + 1;
} else {
sum += arr[l];
l++;
}
}
return sum;
}
public void update(int idx, int val) {
blockSum[idx / blockSize] += (val - arr[idx]);
arr[idx] = val;
}
}
Python
import math
class SqrtDecomposition:
def __init__(self, arr):
self.arr = arr[:]
self.n = len(arr)
self.block_size = max(1, int(math.sqrt(self.n)))
self.block_sum = [0] * ((self.n // self.block_size) + 1)
for i, val in enumerate(arr):
self.block_sum[i // self.block_size] += val
def query(self, l, r):
total = 0
while l <= r:
start_of_block = (l // self.block_size) * self.block_size
end_of_block = min(start_of_block + self.block_size - 1, self.n - 1)
if start_of_block == l and end_of_block <= r:
total += self.block_sum[l // self.block_size]
l = end_of_block + 1
else:
total += self.arr[l]
l += 1
return total
def update(self, idx, val):
self.block_sum[idx // self.block_size] += (val - self.arr[idx])
self.arr[idx] = val
Pseudo Code
function build(arr):
blockSize = sqrt(n)
for i in 0..n-1:
blockSum[i / blockSize] += arr[i]
function query(l, r):
sum = 0
while l <= r:
if l is at the start of its block AND the whole block fits within r:
sum += blockSum[l / blockSize]
l = start of next block
else:
sum += arr[l]
l += 1
return sum
function update(idx, val):
blockSum[idx / blockSize] += val - arr[idx]
arr[idx] = val
Compare Languages Side-by-Side
Pick any two languages below to see the same logic next to each other — useful if you know one of these languages and are mapping the syntax onto the other.
Sqrt Decomposition — Range Sum Query
class SqrtDecomposition {
vector<int> arr;
vector<long long> blockSum;
int n, blockSize;
public:
SqrtDecomposition(vector<int>& input) {
arr = input;
n = arr.size();
blockSize = max(1, (int)sqrt(n));
blockSum.assign((n / blockSize) + 1, 0);
for (int i = 0; i < n; i++) {
blockSum[i / blockSize] += arr[i];
}
}
long long query(int l, int r) {
long long sum = 0;
while (l <= r) {
int startOfBlock = (l / blockSize) * blockSize;
int endOfBlock = min(startOfBlock + blockSize - 1, n - 1);
if (startOfBlock == l && endOfBlock <= r) {
sum += blockSum[l / blockSize];
l = endOfBlock + 1;
} else {
sum += arr[l];
l++;
}
}
return sum;
}
void update(int idx, int val) {
blockSum[idx / blockSize] += (val - arr[idx]);
arr[idx] = val;
}
};
class SqrtDecomposition {
private int[] arr;
private long[] blockSum;
private int n, blockSize;
public SqrtDecomposition(int[] input) {
arr = input.clone();
n = arr.length;
blockSize = Math.max(1, (int) Math.sqrt(n));
blockSum = new long[(n / blockSize) + 1];
for (int i = 0; i < n; i++) {
blockSum[i / blockSize] += arr[i];
}
}
public long query(int l, int r) {
long sum = 0;
while (l <= r) {
int startOfBlock = (l / blockSize) * blockSize;
int endOfBlock = Math.min(startOfBlock + blockSize - 1, n - 1);
if (startOfBlock == l && endOfBlock <= r) {
sum += blockSum[l / blockSize];
l = endOfBlock + 1;
} else {
sum += arr[l];
l++;
}
}
return sum;
}
public void update(int idx, int val) {
blockSum[idx / blockSize] += (val - arr[idx]);
arr[idx] = val;
}
}
Complexity Cheat Sheet
| Operation | Time Complexity | Space Complexity |
|---|---|---|
| Build | ||
| Range Query | - | |
| Point Update | - | |
| Range Update (with lazy block tagging) | - |
When to Use Sqrt Decomposition vs. Segment Tree / Fenwick Tree
- Sqrt Decomposition is simpler to write and reason about, and is often "fast enough" for –.
- Segment Tree and Fenwick Tree offer queries/updates and scale better for very large inputs or when many range updates are needed.
- Sqrt Decomposition is especially handy for problems that don't fit a Segment Tree cleanly — e.g., Mo's Algorithm, answering offline queries, or maintaining more complex per-block state (like a sorted block for order statistics).
Conclusion
Sqrt Decomposition trades a bit of asymptotic performance for a much simpler mental model and implementation. It's a great structure to reach for when a Segment Tree feels like overkill, or as a building block for more advanced techniques like Mo's Algorithm.
Done with this topic? Mark it as complete to track your progress.
💬 Discuss this page
Have a question or spot something confusing in "Sqrt Decomposition"? Ask below — it's backed by GitHub Discussions, so maintainers get notified like any other GitHub activity.