Open In App

Explain an alternative Sorting approach for MO’s Algorithm

Last Updated : 08 Mar, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

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:

  • The block size is calculated as the square root of N which will be used to partition the queries.
  • Then, the queries are sorted according to their left indices and whether the block size is even or odd.
  • A sliding window is created that stores the range of indices from l to r
  • For each query, the elements in the window are updated by adding or removing elements from the range l to r.
  • The variable cnt[a[x]] is used to count the number of times an element x appears in the window.
  • The variable ans[0] is used to keep track of the number of distinct elements in the window.
  • The answer to each query is stored in the array ans[].

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

C++




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




// 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.


Python3




# 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])


C#




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




// 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:

  • Faster Access Time: The double-ended priority queue approach allows for faster access times than other data structures because it can be accessed from both the front and the back of the queue.
  • More Flexibility: Offers more flexibility than other data structures because the data structure allows for both enqueue and dequeue operations, which are typically costly operations. Additionally, the double-ended priority queue approach also allows for the insertion and deletion of elements at any point in the queue.
  • Ease of Use: This approach is relatively straightforward to use and understand. This makes it a good choice for applications that require quick access to data.
  • Improved Performance: It offers improved performance due to the ability to efficiently search in both directions.
  • Flexible Design: This is a flexible design that can be adapted to different needs. It is possible to easily adjust the priority levels for different items in the queue, allowing for greater customization and flexibility.

Disadvantages of the Double-Ended Priority Queue Approach:

  • More Complex: The double-ended priority queue approach is more complex than other data structures and requires more time and effort to implement the data structure correctly.
  • Limited Capacity: The double-ended priority queue approach has limited capacity, meaning it can only hold a certain amount of data. This can be a problem if the queue needs to store large amounts of data, as it will not be able to accommodate it.
  • More Memory: This approach also requires more memory than other data structures because it needs to store both enqueue and dequeue information.

Applications of the Double-Ended Priority Queue Approach:

  • Scheduling: Often used in scheduling applications because the data structure allows for quick access to data and is relatively easy to implement and can also be used to store and access scheduling information, such as deadlines and appointments.
  • Sorting: Also often used in sorting applications because the data structure allows for quick access to data and can also be used to store and access sorted data, such as lists of names or numbers.
  • Data Analysis: This approach is also often used in data analysis applications. This approach can also be used to store and access large amounts of data, such as financial records or customer data.
  • Task Management: The double-ended priority queue approach can be used for task management. It can be used to store tasks in a priority order, making it easier to prioritize them and ensure the most important tasks are completed first.
  • Job Queuing: The double-ended priority queue approach can be used for job queuing to store jobs in priority order, allowing for efficient retrieval of the most important jobs.

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:



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads