Mo’s Algorithm is a square root decomposition technique for answering range queries efficiently. It is especially useful for problems where we need to find distinct elements in a subarray. The algorithm sorts the queries and processes them in a way that minimizes the movement of the window boundaries, resulting in efficient querying.
Steps
Divide the array into blocks of size √n.
Sort the queries based on block number and the right endpoint of the range.
For each query, adjust the current window (move the left or right boundary) to match the range specified in the query while keeping track of the distinct elements.
Return the answer for each query after processing the range.
Time Complexity:
O((n + q) * √n), where n is the length of the array and q is the number of queries.
import mathclass MoAlgorithm: def __init__(self, arr): self.arr = arr self.n = len(arr) self.block_size = int(math.sqrt(self.n)) def count_distinct_in_range(self, queries): result = [] current_l = 0 current_r = -1 freq = [0] * (max(self.arr) + 1) distinct_count = 0 # Function to add a point to the current range def add(x): nonlocal distinct_count freq[x] += 1 if freq[x] == 1: distinct_count += 1 # Function to remove a point from the current range def remove(x): nonlocal distinct_count if freq[x] == 1: distinct_count -= 1 freq[x] -= 1 # Sort queries by block and right endpoint queries.sort(key=lambda q: (q[0] // self.block_size, q[1])) for l, r in queries: while current_r < r: current_r += 1 add(self.arr[current_r]) while current_r > r: remove(self.arr[current_r]) current_r -= 1 while current_l < l: remove(self.arr[current_l]) current_l += 1 while current_l > l: current_l -= 1 add(self.arr[current_l]) result.append(distinct_count) return result# Example usagearr = [1, 2, 1, 3, 2, 1, 3, 2]queries = [(0, 3), (1, 4), (2, 6), (0, 7)]mo = MoAlgorithm(arr)result = mo.count_distinct_in_range(queries)print(f"Distinct elements in each query range: {result}")