Saturday, November 23, 2024
Google search engine
HomeData Modelling & AIMO’s Algorithm (Query Square Root Decomposition) | Set 1 (Introduction)

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
Recommended Practice

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++




// C++ Program to compute sum of ranges for different range
// queries.
#include <bits/stdc++.h>
using namespace std;
 
// Structure to represent a query range
struct Query
{
    int L, R;
};
 
// Prints sum of all query ranges. m is number of queries
// n is the size of the array.
void printQuerySums(int a[], int n, Query q[], int m)
{
    // One by one compute sum of all queries
    for (int i=0; i<m; i++)
    {
        // Left and right boundaries of current range
        int L = q[i].L, R = q[i].R;
 
        // Compute sum of current query range
        int sum = 0;
        for (int j=L; j<=R; j++)
            sum += a[j];
 
        // Print sum of current query range
        cout << "Sum of [" << L << ", "
            << R << "] is "  << sum << endl;
    }
}
 
// Driver program
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




// Java Program to compute sum of ranges for different range
// queries.
import java.util.*;
  
// Class to represent a query range
class Query{
    int L;
    int R;
    Query(int L, int R){
        this.L = L;
        this.R = R;
    }
}
 
class GFG
{
    // Prints sum of all query ranges. m is number of queries
    // n is the size of the array.
    static void printQuerySums(int a[], int n, ArrayList<Query> q, int m)
    {
        // One by one compute sum of all queries
        for (int i=0; i<m; i++)
        {
            // Left and right boundaries of current range
            int L = q.get(i).L, R = q.get(i).R;
     
            // Compute sum of current query range
            int sum = 0;
            for (int j=L; j<=R; j++)
                sum += a[j];
     
            // Print sum of current query range
            System.out.println("Sum of [" + L +
                           ", " + R + "] is "  + sum);
        }
    }
     
    // Driver program
    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);
    }
}
 
// This code is contributed by shivanisinghss2110


Python3




# Python program to compute sum of ranges for different range queries.
 
# Function that accepts array and list of queries and print sum of each query
def printQuerySum(arr,Q):
     
    for q in Q: # Traverse through each query
        L,R = q # Extract left and right indices
        s = 0
        for i in range(L,R+1): # Compute sum of current query range
            s += arr[i]
             
        print("Sum of",q,"is",s) # Print sum of current query range
 
# Driver script
arr = [1, 1, 2, 1, 3, 4, 5, 2, 8]
Q = [[0, 4], [1, 3], [2, 4]]
printQuerySum(arr,Q)
#This code is contributed by Shivam Singh


C#




// C# program to compute sum of ranges for
// different range queries
using System;
using System.Collections;
   
// Class to represent a query range
public class Query
{
    public int L;
    public int R;
     
    public Query(int L, int R)
    {
        this.L = L;
        this.R = R;
    }
}
  
class GFG{
     
// Prints sum of all query ranges. m
//is number of queries n is the size
// of the array.
static void printQuerySums(int []a, int n,
                       ArrayList q, int m)
{
     
    // One by one compute sum of all queries
    for(int i = 0; i < m; i++)
    {
         
        // Left and right boundaries of
        // current range
        int L = ((Query)q[i]).L,
            R = ((Query)q[i]).R;
  
        // Compute sum of current query range
        int sum = 0;
        for(int j = L; j <= R; j++)
            sum += a[j];
             
        // Print sum of current query range
        Console.Write("Sum of [" + L + ", " +
                      R + "] is " + sum + "\n");
    }
}
  
// Driver code
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);
}
}
 
// This code is contributed by pratham76


Javascript




<script>
// Javascript Program to compute sum of ranges for different range
// queries.
 
// Class to represent a query range
class Query{
    constructor(L, R)
    {
        this.L = L;
        this.R = R;
    }
}
 
// Prints sum of all query ranges. m is number of queries
    // n is the size of the array.
function printQuerySums(a, n, q, m)
{
 
    // One by one compute sum of all queries
        for (let i = 0; i < m; i++)
        {
            // Left and right boundaries of current range
            let L = q[i].L, R = q[i].R;
      
            // Compute sum of current query range
            let sum = 0;
            for (let j = L; j <= R; j++)
                sum += a[j];
      
            // Print sum of current query range
            document.write("Sum of [" + L +
                           ", " + R + "] is "  + sum+"<br>");
        }
}
 
// Driver program
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);
 
// This code is contributed by avanitrachhadiya2155
</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. 

  1. 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.
  2. 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++




// Program to compute sum of ranges for different range
// queries
#include <bits/stdc++.h>
using namespace std;
 
// Variable to represent block size. This is made global
// so compare() of sort can use it.
int block;
 
// Structure to represent a query range
struct Query
{
    int L, R;
};
 
// Function used to sort all queries so that all queries
// of the same block are arranged together and within a block,
// queries are sorted in increasing order of R values.
bool compare(Query x, Query y)
{
    // Different blocks, sort by block.
    if (x.L/block != y.L/block)
        return x.L/block < y.L/block;
 
    // Same block, sort by R value
    return x.R < y.R;
}
 
// Prints sum of all query ranges. m is number of queries
// n is size of array a[].
void queryResults(int a[], int n, Query q[], int m)
{
    // Find block size
    block = (int)sqrt(n);
 
    // Sort all queries so that queries of same blocks
    // are arranged together.
    sort(q, q + m, compare);
 
    // Initialize current L, current R and current sum
    int currL = 0, currR = 0;
    int currSum = 0;
 
    // Traverse through all queries
    for (int i=0; i<m; i++)
    {
        // L and R values of current range
        int L = q[i].L, R = q[i].R;
 
        // Remove extra elements of previous range. For
        // example if previous range is [0, 3] and current
        // range is [2, 5], then a[0] and a[1] are subtracted
        while (currL < L)
        {
            currSum -= a[currL];
            currL++;
        }
 
        // Add Elements of current Range
        while (currL > L)
        {
            currSum += a[currL-1];
            currL--;
        }
        while (currR <= R)
        {
            currSum += a[currR];
            currR++;
        }
 
        // Remove elements of previous range.  For example
        // when previous range is [0, 10] and current range
        // is [3, 8], then a[9] and a[10] are subtracted
        while (currR > R+1)
        {
            currSum -= a[currR-1];
            currR--;
        }
 
        // Print sum of current range
        cout << "Sum of [" << L << ", " << R
             << "] is "  << currSum << endl;
    }
}
 
// Driver program
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




// Java Program to compute sum of ranges for
// different range queries
 
import java.util.*;
 
// Class to represent a query range
class Query{
    int L;
    int R;
    Query(int L, int R){
        this.L = L;
        this.R = R;
    }
}
 
class MO{
 
    // Prints sum of all query ranges. m is number of queries
    // n is size of array a[].
    static void queryResults(int a[], int n, ArrayList<Query> q, int m){
         
        // Find block size
        int block = (int) Math.sqrt(n);
     
        // Sort all queries so that queries of same blocks
        // are arranged together.
        Collections.sort(q, new Comparator<Query>(){
             
            // Function used to sort all queries so that all queries 
            // of the same block are arranged together and within a block,
            // queries are sorted in increasing order of R values.
            public int compare(Query x, Query y){
 
                // Different blocks, sort by block.
                if (x.L/block != y.L/block)
                    return (x.L < y.L ? -1 : 1);
 
                // Same block, sort by R value
                return (x.R < y.R ? -1 : 1);
            }
        });
 
        // Initialize current L, current R and current sum
        int currL = 0, currR = 0;
        int currSum = 0;
     
        // Traverse through all queries
        for (int i=0; i<m; i++)
        {
            // L and R values of current range
            int L = q.get(i).L, R = q.get(i).R;
 
            // Remove extra elements of previous range. For
            // example if previous range is [0, 3] and current
            // range is [2, 5], then a[0] and a[1] are subtracted
            while (currL < L)
            {
                currSum -= a[currL];
                currL++;
            }
 
            // Add Elements of current Range
            while (currL > L)
            {
                currSum += a[currL-1];
                currL--;
            }
            while (currR <= R)
            {
                currSum += a[currR];
                currR++;
            }
 
            // Remove elements of previous range.  For example
            // when previous range is [0, 10] and current range
            // is [3, 8], then a[9] and a[10] are subtracted
            while (currR > R+1)
            {
                currSum -= a[currR-1];
                currR--;
            }
 
            // Print sum of current range
            System.out.println("Sum of [" + L +
                           ", " + R + "] is "  + currSum);
        }
    }
 
    // Driver program
    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());
    }
}
// This code is contributed by Ajay


Python3




# Python program to compute sum of ranges for different range queries
 
import math
 
# Function that accepts array and list of queries and print sum of each query
def queryResults(arr,Q):
     
    #Q.sort(): # Sort by L
    #sort all queries so that all queries in the increasing order of R values . 
    Q.sort(key=lambda x: x[1])
     
    # Initialize current L, current R and current sum
    currL,currR,currSum = 0,0,0
     
    # Traverse through all queries
    for i in range(len(Q)):
        L,R = Q[i] # L and R values of current range
         
        # Remove extra elements from previous range
        # if previous range is [0, 3] and current 
        # range is [2, 5], then a[0] and a[1] are subtracted 
        while currL<L:
            currSum-=arr[currL]
            currL+=1
             
        # Add elements of current range
        while currL>L:
            currSum+=arr[currL-1]
            currL-=1
        while currR<=R:
            currSum+=arr[currR]
            currR+=1
             
        # Remove elements of previous range
        # when previous range is [0, 10] and current range 
        # is [3, 8], then a[9] and a[10] are subtracted 
        while currR>R+1:
            currSum-=arr[currR-1]
            currR-=1
         
        # Print the sum of current range
        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)
#This code is contributed by Shivam Singh


C#




// C# Program to compute sum of ranges for different range
// queries
using System;
using System.Collections.Generic;
 
class GFG
{
   
  // Variable to represent block size. This is made global
  // so compare() of sort can use it.
  public static int block;
 
  // Structure to represent a query range
  public struct Query
  {
    public int L;
    public int R;
    public Query(int l, int r)
    {
      L = l;
      R = r;
    }
  }
 
  // Function used to sort all queries so that all queries
  // of the same block are arranged together and within a
  // block, queries are sorted in increasing order of R
  // values.
  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);
    }
  }
 
  // Prints sum of all query ranges. m is number of
  // queries n is size of array a[].
  static void queryResults(int[] a, int n, List<Query> q,
                           int m)
  {
    // Find block size
    block = (int)(Math.Sqrt(n));
 
    // Sort all queries so that queries of same blocks
    // are arranged together.
    q.Sort(new Comparer());
 
    // Initialize current L, current R and current sum
    int currL = 0, currR = 0;
    int currSum = 0;
 
    // Traverse through all queries
    for (int i = 0; i < m; i++) {
      // L and R values of current range
      int L = q[i].L, R = q[i].R;
 
      // Remove extra elements of previous range. For
      // example if previous range is [0, 3] and
      // current range is [2, 5], then a[0] and a[1]
      // are subtracted
      while (currL < L) {
        currSum -= a[currL];
        currL++;
      }
 
      // Add Elements of current Range
      while (currL > L) {
        currSum += a[currL - 1];
        currL--;
      }
      while (currR <= R) {
        currSum += a[currR];
        currR++;
      }
 
      // Remove elements of previous range. For
      // example when previous range is [0, 10] and
      // current range is [3, 8], then a[9] and a[10]
      // are subtracted
      while (currR > R + 1) {
        currSum -= a[currR - 1];
        currR--;
      }
 
      // Print sum of current range
      Console.WriteLine("Sum of [{0}, {1}] is {2}", L,
                        R, currSum);
    }
  }
 
  // Driver program
  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);
  }
}
 
// This code is contributed by cavi4762.


Javascript




// JavaScript program to compute sum of ranges for different range queries
 
// Function that accepts array and list of queries and print sum of each query
function queryResults(arr, Q) {
  // Sort all queries so that all queries in the increasing order of R values
  Q.sort((a, b) => a[1] - b[1]);
 
  // Initialize current L, current R and current sum
  let currL = 0;
  let currR = 0;
  let currSum = 0;
 
  // Traverse through all queries
  for (let i = 0; i < Q.length; i++) {
    const L = Q[i][0];
    const R = Q[i][1];
 
    // Remove extra elements from previous range
    // if previous range is [0, 3] and current
    // range is [2, 5], then a[0] and a[1] are subtracted
    while (currL < L) {
      currSum -= arr[currL];
      currL++;
    }
 
    // Add elements of current range
    while (currL > L) {
      currSum += arr[currL - 1];
      currL--;
    }
    while (currR <= R) {
      currSum += arr[currR];
      currR++;
    }
 
    // Remove elements of previous range
    // when previous range is [0, 10] and current range
    // is [3, 8], then a[9] and a[10] are subtracted
    while (currR > R + 1) {
      currSum -= arr[currR - 1];
      currR--;
    }
 
    // Print the sum of current range
    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: 

  1. All queries are known beforehead so that they can be preprocessed
  2. It cannot work for problems where we have update operations also mixed with sum queries.
  3. 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/
This article is contributed by Ruchir Garg. Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above
 

Feeling lost in the world of random DSA topics, wasting time without progress? It’s time for a change! Join our DSA course, where we’ll guide you on an exciting journey to master DSA efficiently and on schedule.
Ready to dive in? Explore our Free Demo Content and join our DSA course, trusted by over 100,000 neveropen!

RELATED ARTICLES

Most Popular

Recent Comments