0% completed
Mo's Algorithm is designed to efficiently handle query-based problems, particularly those involving range queries on arrays. In such problems, you are often asked to answer multiple queries about different segments of an array. For example, you might need to find the sum, minimum, maximum, or some other property of elements within a specific range of indices.
The Challenge with Query-Based Problems
Without any optimization, solving each query individually can be quite slow. Imagine having an array of size (N) and (Q) queries. A naive approach, where you process each query independently, would result in a time complexity of (O(N \times Q)). This means that for each query, you might end up scanning large portions of the array, leading to slow performance, especially when both (N) and (Q) are large.
The Role of Mo's Algorithm
Mo's Algorithm comes into the picture to solve this efficiency. By cleverly reordering the queries and using a technique called Square Root Decomposition, Mo's Algorithm significantly reduces the number of operations required to answer all queries. This brings down the time complexity to approximately (O((N + Q) \times \sqrt{N})), making it much more feasible to handle a large number of queries on a large array.
Mo's Algorithm is particularly useful when dealing with problems where you need to answer many range queries on static arrays. It optimizes the query processing time and is especially beneficial in competitive programming, where efficiency is crucial. Mo's Algorithm is a go-to solution when you need to handle hundreds of thousands of queries on large datasets.
Understanding Square Root Decomposition
Before we learn Mo's Algorithm, it's important to understand Square Root Decomposition, a key technique that forms the foundation of Mo's Algorithm. Square Root Decomposition is a method used to break down a problem into smaller, more manageable pieces, allowing us to solve certain types of queries more efficiently.
The Idea Behind Square Root Decomposition
The main idea is to divide an array into blocks of approximately equal size. Each block has a length of (\sqrt{N}), where (N) is the size of the array. By doing this, we can process queries over these blocks more quickly, avoiding the need to repeatedly scan the entire array.
Step-by-Step Explanation with Example
Let’s consider an example to see how Square Root Decomposition works.
Suppose you have an array A = [1, 3, 5, 7, 9, 11, 13, 15] and you want to efficiently answer multiple range sum queries, such as "What is the sum of elements from index 2 to 6?"
- Divide the Array into Blocks:
- First, determine the size of each block. If (N = 8), then the block size (B) would be (\sqrt{N} = \sqrt{8} \approx 2.83). We can round this up to 3, so each block will contain 3 elements.
- Precompute Information for Each Block:
- Compute the sum of each block and store it in an auxiliary array:
- Answer Queries Efficiently:
- Now, let's answer the query: "What is the sum of elements from index 2 to 6?"
- Break the query into three parts:
- Partial block before the first complete block: Sum the elements from index 2 to the end of the first block (i.e., sum (A[2])).
- Complete blocks within the query range: Sum the complete blocks that fall within the query range (i.e., sum of Block 2).
- Partial block after the last complete block: Sum the elements from the start of the last block to index 6 (i.e., sum (A[6])).
- For our query:
- Sum (A[2]) = 5 (partial block)
- Sum of Block 2 = 27 (complete block)
- Sum (A[6]) = 13 (partial block)
- Add these sums together to get the total sum: (5 + 27 + 11 = 45).
Step-by-Step Algorithm
-
Preprocessing:
- Calculate the block size as \text{blockSize} = \lceil \sqrt{N} \rceil.
- Initialize a block array of size \text{blockSize} to store sums.
- Iterate through the original array, adding each element to its corresponding block in the block array.
-
Query:
- Initialize
sum
to 0. - Traverse the range
[left, right]
:- Case 1: If the current index
i
is at the start of a block and the block lies completely within the query range, add the precomputed sum of the block tosum
and skip to the next block. - Case 2: Otherwise, add the value of
arr[i]
tosum
and move to the next index.
- Case 1: If the current index
- Return the computed
sum
as the result of the query.
- Initialize
-
Return sum:
- Return the value of sum.
Code
Complexity Analysis
Time Complexity
-
Preprocessing: The time complexity for the preprocessing step, where we calculate the sum of elements for each block, is (O(N)). This involves iterating through the entire array once.
-
Query: The time complexity for each query is (\sqrt{N}). This comes from:
- Processing up to (\sqrt{N}) elements in the partial blocks (at the start and end of the query range).
- Summing the precomputed block sums for the complete blocks within the query range.
Space Complexity
- The space complexity is (\sqrt{N}) for the block array.
Mo's Algorithm
We have already covered Square Root Decomposition, which is a fundamental concept for optimizing certain types of range queries. Building on that, let's explore Mo's Algorithm, a more advanced technique that efficiently handles multiple queries on a static array. Mo's Algorithm is particularly useful when you need to process a large number of queries involving subarrays, such as finding the sum, minimum, maximum, or other properties of elements within a given range.
Step-by-Step Algorithm
- Preprocessing:
- Block Size Calculation: Determine the block size as \text{blockSize} = \sqrt{N}, where (N) is the length of the array.
-
Sorting Queries:
- Block-based Sorting: The array is divided into approximately \sqrt{N} blocks, each of size \sqrt{N}.
- Sorting by Left Endpoint: First, sort the queries based on their left endpoint (
L
). Queries are grouped by the block that their left endpoint falls into. For instance, sort queries withL
values from 0 to \sqrt{N}-1 as one group, then from \sqrt{N} to (2\cdot\sqrt{N}-1) as another group, and so on. - Sorting by Right Endpoint: Within each block, sort the queries by their right endpoint (
R
) in ascending order. This two-level sorting helps minimize the movement of the pointers when transitioning from one query to the next, making the algorithm more efficient.
-
Initialize Variables:
- Set
currentLeft
andcurrentRight
pointers to the beginning of the array (0). - Initialize
currentSum
(or the necessary accumulator) to manage the range starting from the beginning.
- Set
-
Process Each Query:
- For each query, adjust the
currentLeft
andcurrentRight
pointers to match the query’s range[L, R]
:- Expand Right: If
currentRight
is less thanR
, movecurrentRight
to the right and add the element atarr[currentRight]
tocurrentSum
. - Shrink Right: If
currentRight
is greater thanR
, movecurrentRight
to the left and subtract the element atarr[currentRight]
fromcurrentSum
. - Expand Left: If
currentLeft
is less thanL
, movecurrentLeft
to the right and subtract the element atarr[currentLeft]
fromcurrentSum
. - Shrink Left: If
currentLeft
is greater thanL
, movecurrentLeft
to the left and add the element atarr[currentLeft]
tocurrentSum
.
- Expand Right: If
- Store Result: After adjusting the range to match the query, store the result (e.g.,
currentSum
) for the current query.
- For each query, adjust the
-
Output Results:
- Once all queries have been processed, output the stored results for each query in the order they were originally given.
Algorithm Walkthrough
Input and Queries
- Array:
input = {1, 3, 5, 7, 9, 11, 13, 15}
- Queries:
- Query 0: Sum from index 2 to 6.
- Query 1: Sum from index 0 to 4.
- Query 2: Sum from index 3 to 5.
Initial Setup
- Block Size Calculation:
- The block size is calculated as \sqrt{8} \approx 2.83, so we round it to
3
.
- The block size is calculated as \sqrt{8} \approx 2.83, so we round it to
- Sorting Queries:
- Queries are sorted first by their left index (
L
) in blocks, and then by their right index (R
). After sorting:- Query 0: range
[2, 6]
belongs 2/sqrt(8) = 0<sup>th</sup> block. - Query 1: range
[0, 4]
belongs 0/sqrt(8) = 0<sup>th</sup> block. - Query 2: range
[3, 5]
belongs 3/sqrt(8) = 1<sup>st</sup> block.
- Query 0: range
- range
[2, 6]
, and[0, 4]
belongs to the 0<sup>th</sup> block. So, sort them byr
value. Sorted order of them will be[[0, 4], [2, 6]]
. - Sorted order: Queries:
[[0, 4], [2, 6], [3, 5]]
.
- Queries are sorted first by their left index (
Processing Queries
Let’s process the queries in the sorted order.
Processing Query 1: Sum from index 0 to 4
- Initial Pointers:
currentLeft = 0
,currentRight = 0
,currentSum = 1
(sincearr[0] = 1
).
- Expand Right:
- Move
currentRight
from 0 to 4, updatingcurrentSum
:- Add
arr[1] = 3
→currentSum = 4
- Add
arr[2] = 5
→currentSum = 9
- Add
arr[3] = 7
→currentSum = 16
- Add
arr[4] = 9
→currentSum = 25
- Add
- Move
- Store Result:
- Result for Query 1 is
25
.
- Result for Query 1 is
Processing Query 0: Sum from index 2 to 6
- Adjust Left:
- Move
currentLeft
from 0 to 2, updatingcurrentSum
:- Subtract
arr[0] = 1
→currentSum = 24
- Subtract
arr[1] = 3
→currentSum = 21
- Subtract
- Move
- Expand Right:
- Move
currentRight
from 4 to 6, updatingcurrentSum
:- Add
arr[5] = 11
→currentSum = 32
- Add
arr[6] = 13
→currentSum = 45
- Add
- Move
- Store Result:
- Result for Query 0 is
45
.
- Result for Query 0 is
Processing Query 2: Sum from index 3 to 5
- Adjust Left:
- Move
currentLeft
from 2 to 3, updatingcurrentSum
:- Subtract
arr[2] = 5
→currentSum = 40
- Subtract
- Move
- Shrink Right:
- Move
currentRight
from 6 to 5, updatingcurrentSum
:- Subtract
arr[6] = 13
→currentSum = 27
- Subtract
- Move
- Store Result:
- Result for Query 2 is
27
.
- Result for Query 2 is
Final Results
- Query 0:
45
(Sum from index 2 to 6) - Query 1:
25
(Sum from index 0 to 4) - Query 2:
27
(Sum from index 3 to 5)
The final results in order of the original queries are: 45
, 25
, 27
.
Code
Complexity Analysis
Time Complexity
-
Sorting the Queries:
- The queries are sorted based on their block index and right index. Sorting
Q
queries takes O(Q \log Q) time. - This sorting step is essential because Mo's algorithm benefits from processing queries in a specific order, minimizing the movement of the pointers.
- The queries are sorted based on their block index and right index. Sorting
-
Processing Each Query:
-
Mo's algorithm processes each query by adjusting the current range of elements using two pointers (
currentLeft
andcurrentRight
). -
On average, each element is added or removed from the current range about \sqrt{N} times, where
N
is the length of the array. This results in a time complexity of O((N + Q) \times \sqrt{N}) for processing all queries. -
Why (N + Q) \times \sqrt{N}?
- The \sqrt{N} term comes from the fact that each query typically involves moving the left and right pointers a few times. Since there are
N
elements in total, and each can be added or removed O(\sqrt{N}) times, this results in O(N \times \sqrt{N}). - Additionally, since there are
Q
queries and each might involve adjusting the range (i.e., moving the pointers), the processing of queries also contributes O(Q \times \sqrt{N}) to the overall complexity.
- The \sqrt{N} term comes from the fact that each query typically involves moving the left and right pointers a few times. Since there are
-
Combining these, the overall time complexity is O(Q \log Q + (N + Q) \times \sqrt{N})) \approx ((N + Q) \times \sqrt{N})).
Space Complexity
- Space for Results: Storing the results for each query takes (Q) space.
- Auxiliary Space: The array and auxiliary data structures require O(N) space.
Overall Space Complexity: O(N + Q)
When is Mo's Algorithm Applicable?
Mo's Algorithm is applicable in scenarios where:
- You have multiple queries that need to be answered on a static array.
- Each query involves a range of elements, such as a subarray.
- The brute-force approach (processing each query independently) is too slow due to the large size of the array and the number of queries.
- The array is static, meaning that there are no updates between queries.
Examples of problems where Mo's Algorithm is applicable include:
- Range sum queries.
- Finding the frequency of an element in a subarray.
- Counting distinct elements within a range.
.....
.....
.....
Table of Contents
Contents are not accessible
Contents are not accessible
Contents are not accessible
Contents are not accessible
Contents are not accessible