MO’s Algorithm (Query Square Root Decomposition) | Set 1 (Introduction)
Let us consider the following problem to understand MO’s Algorithm.
We are given an array and a set of query ranges, we are required to find the sum of every query range.
Example:
Input: arr[] = {1, 1, 2, 1, 3, 4, 5, 2, 8};
query[] = [0, 4], [1, 3] [2, 4]
Output: Sum of arr[] elements in range [0, 4] is 8
Sum of arr[] elements in range [1, 3] is 4
Sum of arr[] elements in range [2, 4] is 6
A Naive Solution is to run a loop from L to R and calculate the sum of elements in given range for every query [L, R]
C++
#include <bits/stdc++.h>
using namespace std;
struct Query
{
int L, R;
};
void printQuerySums( int a[], int n, Query q[], int m)
{
for ( int i=0; i<m; i++)
{
int L = q[i].L, R = q[i].R;
int sum = 0;
for ( int j=L; j<=R; j++)
sum += a[j];
cout << "Sum of [" << L << ", "
<< R << "] is " << sum << endl;
}
}
int main()
{
int a[] = {1, 1, 2, 1, 3, 4, 5, 2, 8};
int n = sizeof (a)/ sizeof (a[0]);
Query q[] = {{0, 4}, {1, 3}, {2, 4}};
int m = sizeof (q)/ sizeof (q[0]);
printQuerySums(a, n, q, m);
return 0;
}
|
Java
import java.util.*;
class Query{
int L;
int R;
Query( int L, int R){
this .L = L;
this .R = R;
}
}
class GFG
{
static void printQuerySums( int a[], int n, ArrayList<Query> q, int m)
{
for ( int i= 0 ; i<m; i++)
{
int L = q.get(i).L, R = q.get(i).R;
int sum = 0 ;
for ( int j=L; j<=R; j++)
sum += a[j];
System.out.println( "Sum of [" + L +
", " + R + "] is " + sum);
}
}
public static void main(String argv[])
{
int a[] = { 1 , 1 , 2 , 1 , 3 , 4 , 5 , 2 , 8 };
int n = a.length;
ArrayList<Query> q = new ArrayList<Query>();
q.add( new Query( 0 , 4 ));
q.add( new Query( 1 , 3 ));
q.add( new Query( 2 , 4 ));
int m = q.size();
printQuerySums(a, n, q, m);
}
}
|
Python3
def printQuerySum(arr,Q):
for q in Q:
L,R = q
s = 0
for i in range (L,R + 1 ):
s + = arr[i]
print ( "Sum of" ,q, "is" ,s)
arr = [ 1 , 1 , 2 , 1 , 3 , 4 , 5 , 2 , 8 ]
Q = [[ 0 , 4 ], [ 1 , 3 ], [ 2 , 4 ]]
printQuerySum(arr,Q)
|
C#
using System;
using System.Collections;
public class Query
{
public int L;
public int R;
public Query( int L, int R)
{
this .L = L;
this .R = R;
}
}
class GFG{
static void printQuerySums( int []a, int n,
ArrayList q, int m)
{
for ( int i = 0; i < m; i++)
{
int L = ((Query)q[i]).L,
R = ((Query)q[i]).R;
int sum = 0;
for ( int j = L; j <= R; j++)
sum += a[j];
Console.Write( "Sum of [" + L + ", " +
R + "] is " + sum + "\n" );
}
}
public static void Main( string []argv)
{
int []a = { 1, 1, 2, 1, 3, 4, 5, 2, 8 };
int n = a.Length;
ArrayList q = new ArrayList();
q.Add( new Query(0, 4));
q.Add( new Query(1, 3));
q.Add( new Query(2, 4));
int m = q.Count;
printQuerySums(a, n, q, m);
}
}
|
Javascript
<script>
class Query{
constructor(L, R)
{
this .L = L;
this .R = R;
}
}
function printQuerySums(a, n, q, m)
{
for (let i = 0; i < m; i++)
{
let L = q[i].L, R = q[i].R;
let sum = 0;
for (let j = L; j <= R; j++)
sum += a[j];
document.write( "Sum of [" + L +
", " + R + "] is " + sum+ "<br>" );
}
}
let a = [1, 1, 2, 1, 3, 4, 5, 2, 8];
let n = a.length;
let q = [];
q.push( new Query(0,4));
q.push( new Query(1,3));
q.push( new Query(2,4));
let m = q.length;
printQuerySums(a, n, q, m);
</script>
|
Output:
Sum of [0, 4] is 8
Sum of [1, 3] is 4
Sum of [2, 4] is 6
Time Complexity: O(mn).
Auxiliary Space: O(1), since no extra space has been taken.
The idea of MO’s algorithm is to pre-process all queries so that result of one query can be used in next query. Below are steps.
Let a[0…n-1] be input array and q[0..m-1] be array of queries.
- Sort all queries in a way that queries with L values from 0 to ?n – 1 are put together, then all queries from ?n to 2*?n – 1, and so on. All queries within a block are sorted in increasing order of R values.
- Process all queries one by one in a way that every query uses sum computed in the previous query.
- Let ‘sum’ be sum of previous query.
- Remove extra elements of previous query. For example if previous query is [0, 8] and current query is [3, 9], then we subtract a[0],a[1] and a[2] from sum
- Add new elements of current query. In the same example as above, we add a[9] to sum.
The great thing about this algorithm is, in step 2, index variable for R change at most O(n * ?n) times throughout the run and same for L changes its value at most O(m * ?n) times (See below, after the code, for details). All these bounds are possible only because the queries are sorted first in blocks of ?n size.
The preprocessing part takes O(m Log m) time.
Processing all queries takes O(n * ?n) + O(m * ?n) = O((m+n) * ?n) time.
Below is the implementation of the above idea.
C++
#include <bits/stdc++.h>
using namespace std;
int block;
struct Query
{
int L, R;
};
bool compare(Query x, Query y)
{
if (x.L/block != y.L/block)
return x.L/block < y.L/block;
return x.R < y.R;
}
void queryResults( int a[], int n, Query q[], int m)
{
block = ( int ) sqrt (n);
sort(q, q + m, compare);
int currL = 0, currR = 0;
int currSum = 0;
for ( int i=0; i<m; i++)
{
int L = q[i].L, R = q[i].R;
while (currL < L)
{
currSum -= a[currL];
currL++;
}
while (currL > L)
{
currSum += a[currL-1];
currL--;
}
while (currR <= R)
{
currSum += a[currR];
currR++;
}
while (currR > R+1)
{
currSum -= a[currR-1];
currR--;
}
cout << "Sum of [" << L << ", " << R
<< "] is " << currSum << endl;
}
}
int main()
{
int a[] = {1, 1, 2, 1, 3, 4, 5, 2, 8};
int n = sizeof (a)/ sizeof (a[0]);
Query q[] = {{0, 4}, {1, 3}, {2, 4}};
int m = sizeof (q)/ sizeof (q[0]);
queryResults(a, n, q, m);
return 0;
}
|
Java
import java.util.*;
class Query{
int L;
int R;
Query( int L, int R){
this .L = L;
this .R = R;
}
}
class MO{
static void queryResults( int a[], int n, ArrayList<Query> q, int m){
int block = ( int ) Math.sqrt(n);
Collections.sort(q, new Comparator<Query>(){
public int compare(Query x, Query y){
if (x.L/block != y.L/block)
return (x.L < y.L ? - 1 : 1 );
return (x.R < y.R ? - 1 : 1 );
}
});
int currL = 0 , currR = 0 ;
int currSum = 0 ;
for ( int i= 0 ; i<m; i++)
{
int L = q.get(i).L, R = q.get(i).R;
while (currL < L)
{
currSum -= a[currL];
currL++;
}
while (currL > L)
{
currSum += a[currL- 1 ];
currL--;
}
while (currR <= R)
{
currSum += a[currR];
currR++;
}
while (currR > R+ 1 )
{
currSum -= a[currR- 1 ];
currR--;
}
System.out.println( "Sum of [" + L +
", " + R + "] is " + currSum);
}
}
public static void main(String argv[]){
ArrayList<Query> q = new ArrayList<Query>();
q.add( new Query( 0 , 4 ));
q.add( new Query( 1 , 3 ));
q.add( new Query( 2 , 4 ));
int a[] = { 1 , 1 , 2 , 1 , 3 , 4 , 5 , 2 , 8 };
queryResults(a, a.length, q, q.size());
}
}
|
Python3
import math
def queryResults(arr,Q):
Q.sort(key = lambda x: x[ 1 ])
currL,currR,currSum = 0 , 0 , 0
for i in range ( len (Q)):
L,R = Q[i]
while currL<L:
currSum - = arr[currL]
currL + = 1
while currL>L:
currSum + = arr[currL - 1 ]
currL - = 1
while currR< = R:
currSum + = arr[currR]
currR + = 1
while currR>R + 1 :
currSum - = arr[currR - 1 ]
currR - = 1
print ( "Sum of" ,Q[i], "is" ,currSum)
arr = [ 1 , 1 , 2 , 1 , 3 , 4 , 5 , 2 , 8 ]
Q = [[ 0 , 4 ], [ 1 , 3 ], [ 2 , 4 ]]
queryResults(arr,Q)
|
C#
using System;
using System.Collections.Generic;
class GFG
{
public static int block;
public struct Query
{
public int L;
public int R;
public Query( int l, int r)
{
L = l;
R = r;
}
}
public class Comparer : IComparer<Query> {
public int Compare(Query x, Query y)
{
int ret = ( int )(x.L / block)
.CompareTo(( int )(y.L / block));
return ret != 0 ? ret : x.R.CompareTo(y.R);
}
}
static void queryResults( int [] a, int n, List<Query> q,
int m)
{
block = ( int )(Math.Sqrt(n));
q.Sort( new Comparer());
int currL = 0, currR = 0;
int currSum = 0;
for ( int i = 0; i < m; i++) {
int L = q[i].L, R = q[i].R;
while (currL < L) {
currSum -= a[currL];
currL++;
}
while (currL > L) {
currSum += a[currL - 1];
currL--;
}
while (currR <= R) {
currSum += a[currR];
currR++;
}
while (currR > R + 1) {
currSum -= a[currR - 1];
currR--;
}
Console.WriteLine( "Sum of [{0}, {1}] is {2}" , L,
R, currSum);
}
}
static void Main( string [] args)
{
int [] a = { 1, 1, 2, 1, 3, 4, 5, 2, 8 };
int n = a.Length;
List<Query> q = new List<Query>();
q.Add( new Query(0, 4));
q.Add( new Query(1, 3));
q.Add( new Query(2, 4));
int m = q.Count;
queryResults(a, n, q, m);
}
}
|
Javascript
function queryResults(arr, Q) {
Q.sort((a, b) => a[1] - b[1]);
let currL = 0;
let currR = 0;
let currSum = 0;
for (let i = 0; i < Q.length; i++) {
const L = Q[i][0];
const R = Q[i][1];
while (currL < L) {
currSum -= arr[currL];
currL++;
}
while (currL > L) {
currSum += arr[currL - 1];
currL--;
}
while (currR <= R) {
currSum += arr[currR];
currR++;
}
while (currR > R + 1) {
currSum -= arr[currR - 1];
currR--;
}
console.log(`Sum of ${Q[i]} is ${currSum}`);
}
}
const arr = [1, 1, 2, 1, 3, 4, 5, 2, 8];
const Q = [[1, 3], [0, 4], [2, 4]];
queryResults(arr, Q);
|
Output:
Sum of [1, 3] is 4
Sum of [0, 4] is 8
Sum of [2, 4] is 6
The output of above program doesn’t print results of queries in same order as input, because queries are sorted. The program can be easily extended to keep the same order.
Important Observations:
- All queries are known beforehead so that they can be preprocessed
- It cannot work for problems where we have update operations also mixed with sum queries.
- MO’s algorithm can only be used for query problems where a query can be computed from results of the previous query. One more such example is maximum or minimum.
Time Complexity Analysis:
The function mainly runs a for loop for all sorted queries. Inside the for loop, there are four while queries that move ‘currL’ and ‘currR’.
How much currR is moved? For each block, queries are sorted in increasing order of R. So, for a block, currR moves in increasing order. In worst case, before beginning of every block, currR at extreme right and current block moves it back the extreme left. This means that for every block, currR moves at most O(n). Since there are O(?n) blocks, total movement of currR is O(n * ?n).
How much currL is moved? Since all queries are sorted in a way that L values are grouped by blocks, movement is O(?n) when we move from one query to another quert. For m queries, total movement of currL is O(m * ?n)
Please note that a Simple and more Efficient solution to solve this problem is to compute prefix sum for all elements from 0 to n-1. Let the prefix sum be stored in an array preSum[] (The value of preSum[i] stores sum of arr[0..i]). Once we have built preSum[], we can traverse through all queries one by one. For every query [L, R], we return value of preSum[R] – preSum[L]. Here processing every query takes O(1) time.
Auxiliary Space: O(1), since no extra space has been taken.
The idea of this article is to introduce MO’s algorithm with a very simple example. We will soon be discussing more interesting problems using MO’s algorithm.
Range Minimum Query (Square Root Decomposition and Sparse Table)
References:
http://blog.anudeep2011.com/mos-algorithm/
Last Updated :
19 Jan, 2023
Like Article
Save Article
Share your thoughts in the comments
Please Login to comment...