Open In App

Explain an alternative Sorting approach for MO’s Algorithm

MO’s Algorithm is an algorithm designed to efficiently answer range queries in an array in linear time. It is a divide-and-conquer approach that involves pre-processing the array, partitioning it into blocks, and then solving the queries in each of the blocks.

Alternate Approach for Sorting:

An alternate approach that has been proposed is known as the double-ended priority queue (DEQ) approach. This approach uses a double-ended priority queue (DEQ) to store the elements of the array. The elements are then sorted using the DEQ, which allows for faster retrieval of elements.



Double-Ended Priority Queue (DEQ):

A double-ended priority queue (DEQ) is a data structure that stores items in an order of priority and allows access from both the front and back of the queue.

The DEQ allows for both the insertion and deletion of elements in O(log n) time, where n is the number of elements in the DEQ. This makes it an efficient data structure for maintaining the order of elements in a range as it allows for fast insertion and deletion of elements.



Consider an array that consists of N elements and there are Q queries where each query consists of two indices l and r denoting the range of the query.

Steps involved in the implementation of code:

Below is the implementation of Double ended Priority Queue (DEQ):




// C++ code to implement the approaach
 
#include <bits/stdc++.h>
using namespace std;
 
// N is the maximum number of elements
// in the array
const int N = 1e5 + 5;
int n, q, a[N], ans[N], cnt[N], block_size;
 
// Query structure for storing query details
struct query {
    int l, r, id;
} qry[N];
 
// Function to compare two queries
bool cmp(query a, query b)
{
    // If the query is in different blocks,
    // sort according to lth index
    if (a.l / block_size != b.l / block_size)
        return a.l < b.l;
 
    // If the queries are in the same block,
    // sort according to the parity of the
    // block number
    return (a.l / block_size & 1) ? a.r < b.r : a.r > b.r;
}
 
// Function to add an element to the
// current range
void add(int x)
{
    cnt[a[x]]++;
 
    // Increase the answer if the element
    // is added for the first time in
    // this range
    if (cnt[a[x]] == 1)
        ans[0]++;
}
 
// Function to remove an element from
// the current range
void remove(int x)
{
    cnt[a[x]]--;
 
    // Decrease the answer if the element
    // is completely removed from
    // this range
    if (cnt[a[x]] == 0)
        ans[0]--;
}
 
// Driver code
int main()
{
    n = 12;
    q = 3;
    block_size = sqrt(n);
 
    // Example array
    int array[] = { 1, 2, 2, 2, 3, 4, 5, 5, 5, 6, 6, 6 };
 
    // Store the elements in the array
    for (int i = 0; i < n; i++)
        a[i + 1] = array[i];
 
    // Example range queries
    int l1 = 1, r1 = 4, l2 = 2, r2 = 6, l3 = 3, r3 = 8;
 
    // Store the query details
    qry[0].l = l1;
    qry[0].r = r1;
    qry[0].id = 0;
    qry[1].l = l2;
    qry[1].r = r2;
    qry[1].id = 1;
    qry[2].l = l3;
    qry[2].r = r3;
    qry[2].id = 2;
 
    // Sort the queries according to their
    // lth and rth indices
    sort(qry, qry + q, cmp);
 
    // l and r denote the current range
    int l = 1, r = 0;
 
    // Iterate through all the queries
    for (int i = 0; i < q; i++) {
 
        // Expand the range to the left
        while (l > qry[i].l)
            add(--l);
 
        // Expand the range to the right
        while (r < qry[i].r)
            add(++r);
 
        // Shrink the range from the left
        while (l < qry[i].l)
            remove(l++);
 
        // Shrink the range from the right
        while (r > qry[i].r)
            remove(r--);
 
        // Store the answer for the
        // current query
        ans[qry[i].id] = ans[0];
    }
 
    // Print answers to the queries
    for (int i = 0; i < q; i++)
        cout << ans[i] << endl;
 
    return 0;
}




// Java code to implement the approaach
import java.util.*;
 
public class Main {
    static class Query {
        int l, r, id;
 
        // Query structure for storing query details
        Query(int l, int r, int id) {
            this.l = l;
            this.r = r;
            this.id = id;
        }
    }
 
    static int n, q, block_size;
    static int[] a, ans, cnt;
 
    // Function to compare two queries
    static class QueryComparator implements Comparator<Query> {
        @Override
        public int compare(Query a, Query b) {
            // If the query is in different blocks,
            // sort according to lth index
            if (a.l / block_size != b.l / block_size) {
                return Integer.compare(a.l, b.l);
            }
            // If the queries are in the same block,
            // sort according to the parity of the
            // block number
            return ((a.l / block_size) & 1) == 0 ? Integer.compare(a.r, b.r) : Integer.compare(b.r, a.r);
        }
    }
 
    // Function to add an element to the
    // current range
    static void add(int x) {
        cnt[a[x]]++;
         
        // Increase the answer if the element
        // is added for the first time in
        // this range
        if (cnt[a[x]] == 1) {
            ans[0]++;
        }
    }
 
    // Function to remove an element from
    // the current range
    static void remove(int x) {
        cnt[a[x]]--;
         
        // Decrease the answer if the element
        // is completely removed from
        // this range
        if (cnt[a[x]] == 0) {
            ans[0]--;
        }
    }
 
    // Driver code
    public static void main(String[] args) {
        n = 12;
        q = 3;
        block_size = (int) Math.sqrt(n);
 
        // Example array
        int[] array = {1, 2, 2, 2, 3, 4, 5, 5, 5, 6, 6, 6};
 
        // Store the elements in the array
        a = new int[n + 1];
        for (int i = 0; i < n; i++) {
            a[i + 1] = array[i];
        }
 
        // Example range queries
        int l1 = 1, r1 = 4, l2 = 2, r2 = 6, l3 = 3, r3 = 8;
 
        // Store the query details
        Query[] qry = new Query[q];
        qry[0] = new Query(l1, r1, 0);
        qry[1] = new Query(l2, r2, 1);
        qry[2] = new Query(l3, r3, 2);
 
        // Sort the queries according to their
        // lth and rth indices
        Arrays.sort(qry, new QueryComparator());
 
        // l and r denote the current range
        int l = 1, r = 0;
        cnt = new int[n];
        ans = new int[q + 1];
 
        // Iterate through all the queries
        for (int i = 0; i < q; i++) {
            // Expand the range to the left
            while (l > qry[i].l)
                add(--l);
 
            // Expand the range to the right
            while (r < qry[i].r)
                add(++r);
 
            // Shrink the range from the left
            while (l < qry[i].l)
                remove(l++);
 
            // Shrink the range from the right
            while (r > qry[i].r)
                remove(r--);
 
            // Store the answer for the
            // current query
           ans[qry[i].id] = ans[0];
        }
         
        // Print answers to the queries
        for (int i = 0; i < q; i++)
            System.out.println(ans[i]);
    }
}
 
// This code is contributed by Pushpesh Raj.




# Python code to implement the approaach
 
import math
 
# N is the maximum number of elements
# in the array
N = int(1e5) + 5
n, q, block_size = 0, 0, 0
a = [0] * N
ans = [0] * N
cnt = [0] * N
 
# Query structure for storing query details
class Query:
    def __init__(self, l, r, id):
        self.l = l
        self.r = r
        self.id = id
 
# Function to compare two queries
def cmp(a, b):
    # If the query is in different blocks,
    # sort according to lth index
    if a.l // block_size != b.l // block_size:
        return a.l < b.l
 
    # If the queries are in the same block,
    # sort according to the parity of the
    # block number
    return a.r < b.r if a.l // block_size & 1 else a.r > b.r
 
# Function to add an element to the
# current range
def add(x):
    cnt[a[x]] += 1
 
    # Increase the answer if the element
    # is added for the first time in
    # this range
    if cnt[a[x]] == 1:
        ans[0] += 1
 
# Function to remove an element from
# the current range
def remove(x):
    cnt[a[x]] -= 1
 
    # Decrease the answer if the element
    # is completely removed from
    # this range
    if cnt[a[x]] == 0:
        ans[0] -= 1
 
# Driver code
if __name__ == '__main__':
    n = 12
    q = 3
    block_size = int(math.sqrt(n))
 
    # Example array
    array = [1, 2, 2, 2, 3, 4, 5, 5, 5, 6, 6, 6]
 
    # Store the elements in the array
    for i in range(n):
        a[i + 1] = array[i]
 
    # Example range queries
    l1, r1, l2, r2, l3, r3 = 1, 4, 2, 6, 3, 8
 
    # Store the query details
    qry = [Query(l1, r1, 0), Query(l2, r2, 1), Query(l3, r3, 2)]
 
    # Sort the queries according to their
    # lth and rth indices
    qry.sort(key=lambda x: cmp(x, x))
 
    # l and r denote the current range
    l, r = 1, 0
 
    # Iterate through all the queries
    for i in range(q):
        # Expand the range to the left
        while l > qry[i].l:
            add(l-1)
            l -= 1
 
        # Expand the range to the right
        while r < qry[i].r:
            add(r+1)
            r += 1
 
        # Shrink the range from the left
        while l < qry[i].l:
            remove(l)
            l += 1
 
        # Shrink the range from the right
        while r > qry[i].r:
            remove(r)
            r -= 1
 
        # Store the answer for the
        # current query
        ans[qry[i].id] = ans[0]
 
    # Print answers to the queries
    for i in range(q):
        print(ans[i])




using System;
 
public class Program
{
  // N is the maximum number of elements
  // in the array
  const int N = 100005;
  static int n, q, block_size;
  static int[] a = new int[N], ans = new int[N], cnt = new int[N];
  // Query structure for storing query details
  struct Query
  {
    public int l, r, id;
 
    public Query(int l, int r, int id)
    {
      this.l = l;
      this.r = r;
      this.id = id;
    }
  }
 
  // Function to compare two queries
  static int cmp(Query a, Query b)
  {
    // If the query is in different blocks,
    // sort according to lth index
    if (a.l / block_size != b.l / block_size)
    {
      return a.l / block_size - b.l / block_size;
    }
    // If the queries are in the same block,
    // sort according to the parity of the
    // block number
    return (a.l / block_size & 1) != 0 ? a.r - b.r : b.r - a.r;
  }
  // Function to add an element to the
  // current range
  static void add(int x)
  {
    cnt[a[x]]++;
    // Increase the answer if the element
    // is added for the first time in
    // this range
    if (cnt[a[x]] == 1)
    {
      ans[0]++;
    }
  }
  // Function to remove an element from
  // the current range
  static void remove(int x)
  {
    // Decrease the answer if the element
    // is completely removed from
    // this range
    cnt[a[x]]--;
 
    if (cnt[a[x]] == 0)
    {
      ans[0]--;
    }
  }
  // Driver code
  public static void Main()
  {
    n = 12;
    q = 3;
    block_size = (int)Math.Sqrt(n);
 
    int[] array = { 1, 2, 2, 2, 3, 4, 5, 5, 5, 6, 6, 6 };
 
    // Store the elements in the array
    for (int i = 0; i < n; i++)
    {
      a[i + 1] = array[i];
    }
    // Example range queries
    Query[] qry = {
      new Query(1, 4, 0),
      new Query(2, 6, 1),
      new Query(3, 8, 2)
      };
 
    // Sort the queries according to their
    // lth and rth indices
    Array.Sort(qry, cmp);
    // l and r denote the current range
    int l = 1, r = 0;
    // Iterate through all the queries
    for (int i = 0; i < q; i++)
    {
      // Expand the range to the left
      while (l > qry[i].l)
      {
        add(--l);
      }
      // Expand the range to the right
      while (r < qry[i].r)
      {
        add(++r);
      }
      // Shrink the range from the left
      while (l < qry[i].l)
      {
        remove(l++);
      }
      // Shrink the range from the right
      while (r > qry[i].r)
      {
        remove(r--);
      }
      // Store the answer for the
      // current query
      ans[qry[i].id] = ans[0];
    }
    //Print answer
    for (int i = 0; i < q; i++)
    {
      Console.WriteLine(ans[i]);
    }
  }
}




// Javascript code to implement the approaach
 
// N is the maximum number of elements
// in the array
const N = 1e5 + 5;
let n = 0, q = 0, block_size = 0;
const a = new Array(N).fill(0);
const ans = new Array(N).fill(0);
const cnt = new Array(N).fill(0);
 
// Query structure for storing query details
class Query {
  constructor(l, r, id) {
    this.l = l;
    this.r = r;
    this.id = id;
  }
}
 
 
// Function to compare two queries
function cmp(a, b) {
    // If the query is in different blocks,
    // sort according to lth index
  if (Math.floor(a.l / block_size) !== Math.floor(b.l / block_size)) {
    return a.l < b.l;
  }
    // If the queries are in the same block,
    // sort according to the parity of the
    // block number
  return a.r < b.r ? (a.l % 2 === 0 ? 1 : -1) * (a.r - b.r) : (a.l % 2 === 0 ? 1 : -1) * (b.r - a.r);
}
 
 
// Function to add an element to the
// current range
function add(x) {
     
  // Increase the answer if the element
  // is added for the first time in
  // this range
  cnt[a[x]]++;
  if (cnt[a[x]] === 1) {
    ans[0]++;
  }
}
 
 
// Function to remove an element from
// the current range
function remove(x) {
  cnt[a[x]]--;
  // Decrease the answer if the element
  // is completely removed from
  // this range
  if (cnt[a[x]] === 0) {
    ans[0]--;
  }
}
 
 
// Driver code
n = 12;
q = 3;
block_size = Math.floor(Math.sqrt(n));
 
// Example array
const array = [1, 2, 2, 2, 3, 4, 5, 5, 5, 6, 6, 6];
 
// Store the elements in the array
for (let i = 0; i < n; i++) {
  a[i + 1] = array[i];
}
 
// Example range queries
const l1 = 1, r1 = 4, l2 = 2, r2 = 6, l3 = 3, r3 = 8;
// Store the query details
const qry = [new Query(l1, r1, 0), new Query(l2, r2, 1), new Query(l3, r3, 2)];
 
// Sort the queries according to their
// lth and rth indices
qry.sort(cmp);
 
 
// l and r denote the current range
let l = 1, r = 0;
 
// Iterate through all the queries
for (let i = 0; i < q; i++) {
     
  // Expand the range to the left
  while (l > qry[i].l) {
       
    add(l - 1);
    l--;
  }
   
  // Expand the range to the right
  while (r < qry[i].r) {
    add(r + 1);
    r++;
  }
   
  // Shrink the range from the left
  while (l < qry[i].l) {
    remove(l);
    l++;
  }
   
  // Shrink the range from the right
  while (r > qry[i].r) {
    remove(r);
    r--;
  }
   
  // Store the answer for the
  // current query
  ans[qry[i].id] = ans[0];
}
 
// Print answers to the queries
for (let i = 0; i < q; i++) {
  console.log(ans[i]);
}
 
// This code is contributed by sdeadityasharma

Output
4
3
4

Time Complexity: O(Q * sqrt(N) + N*log N + Q*logQ)
Auxiliary Space: O(N)

Advantages of the Double-Ended Priority Queue Approach:

Disadvantages of the Double-Ended Priority Queue Approach:

Applications of the Double-Ended Priority Queue Approach:

Conclusion:

The double-ended priority queue (DEQ) approach is an alternate sorting approach for MO’s algorithm. It has several advantages over the block sorting approach, such as being able to sort elements in both ascending and descending order, being more efficient, and requiring less storage space. However, it also has some drawbacks, such as requiring extra memory, being limited to a certain number of elements, and not being as flexible as block sorting. Despite these drawbacks, it may still be useful in certain applications due to its advantages.

Related Articles:


Article Tags :