Square root decomposition (Mo's algorithm)
Benefit
Section titled “Benefit”Allows to reduce the time complexity of linear scan to
When 2 use ?
Section titled “When 2 use ?”To be used in scenarios when we want to scan an array but the linear scan is expensive, but the scan also works if performed on groups of consecutive elements.
Algorithm
Section titled “Algorithm”Divide the array into number of groups, with group size . And each group has value obtained by some operation on it’s elements.
Now for each of these groups, we scan a group by making use of it’s value and our condition.
Space and time complexities
Section titled “Space and time complexities”| Find operation T.C. | |
| Update operation T.C. | |
| Space complexity |
Example
Section titled “Example”We want to scan an array to find the leftmost value .
Let’s say with elements.
Normal scan will be , but if we split the array into groups of we can reduce it to :
Step 1: Create Groups
Section titled “Step 1: Create Groups”Group size =
Number of groups =
Original Array: [2, 4, 3, 6, 7, 5, 8, 9, 1, 10, 12, 4, 6, 8, 3, 11]
Group 0: [2, 4, 3, 6] → max = 6Group 1: [7, 5, 8, 9] → max = 9Group 2: [1, 10, 12, 4] → max = 12Group 3: [6, 8, 3, 11] → max = 11Step 2: Query Processing
Section titled “Step 2: Query Processing”Query: Find leftmost element
-
Scan Group 0 (max = 6):
- 6 < 10, skip entire group ✗
-
Scan Group 1 (max = 9):
- 9 < 10, skip entire group ✗
-
Scan Group 2 (max = 12):
- 12 ≥ 10, search inside group ✓
- Check elements: 1 < 10, 10 ≥ 10 ✓
- Answer: Index 9, Value = 10
Analysis:
- Groups checked: 3 groups
- Elements scanned: 2 elements (inside Group 2)
- Total operations:
- Without decomposition: Would need to check all 10 elements
Step 3: More Examples
Section titled “Step 3: More Examples”Query: Find leftmost element
- Group 0 (max = 6): 6 ≥ 5 ✓
- Scan: 2 < 5, 4 < 5, 3 < 5, 6 ≥ 5 ✓
- Answer: Index 3, Value = 6
Query: Find leftmost element
- Group 0 (max = 6): Skip
- Group 1 (max = 9): Skip
- Group 2 (max = 12): Skip
- Group 3 (max = 11): Skip
- Answer: Not found
Complexity Analysis
Section titled “Complexity Analysis”| Operation | Time Complexity |
|---|---|
| Preprocessing (build groups) | |
| Query (find element) | |
| Update element | for value, for group max |
| Space | for group metadata |
Trade-off:
- Linear scan: query, update
- Square root decomposition: query, update
- Segment tree: query, update (but more complex)
Implementation
Section titled “Implementation”int[] arr = {2, 4, 3, 6, 7, 5, 8, 9, 1, 10, 12, 4, 6, 8, 3, 11};int n = arr.length;int blockSize = (int) Math.sqrt(n); // 4int[] groupMax = new int[4]; // Store max of each group
// Build: Compute max for each group - O(n)for (int i = 0; i < n; i++) { int block = i / blockSize; groupMax[block] = Math.max(groupMax[block], arr[i]);}
// Query: Find leftmost element >= target - O(√n)int findLeftmost(int target) { for (int i = 0; i < groupMax.length; i++) { if (groupMax[i] < target) continue; // Skip group
// Search inside group for (int j = i * blockSize; j < Math.min((i + 1) * blockSize, n); j++) { if (arr[j] >= target) return j; } } return -1;}
// Update: Change value and rebuild group - O(√n)void update(int index, int value) { arr[index] = value; int block = index / blockSize;
// Recalculate group max groupMax[block] = 0; for (int i = block * blockSize; i < Math.min((block + 1) * blockSize, n); i++) { groupMax[block] = Math.max(groupMax[block], arr[i]); }}Use Cases
Section titled “Use Cases”-
Range queries on static/semi-static data
- When segment trees are overkill
- Simple implementation needed
-
Balance between preprocessing and query time
- Not as fast as segment tree ( vs )
- But simpler to implement and understand
-
Memory-efficient solutions
- Uses less space than segment trees
- Good for competitive programming
-
Problems with complex operations
- Operations that don’t easily decompose into segment tree logic
- Custom aggregation functions