Skip to main content

MO’s algorithm and its applications

 

Before learning MO’s Algorithm, let us take a quick look at the prerequisites through which we can solve it.

In competitive programming, we come across questions which involves queries mostly static queries which are given as vectors or arrays. Each such queries represent a run in the code. So, do you think running the code each time for every query operation is fine? Obviously, it is a brute force approach and it will fail most of the times in a coding platform as there will be a huge number of queries. So, what we can do? Can we always break up the array in two parts for like we do in Binary Search? Not really! And nor it will be beneficial.

Let us consider a particular part of the array may be some xth part of the array size. We divide the array into x parts to reduce our time complexity. By using complex mathematical calculations – differentiation we found out that this x is nothing but, where n is the size of the array. We come to the conclusion that dividing the array into blocks of elements help to reduce the time complexity significantly.

This very technique is called Square root Decomposition (Don’t worry we will need this in Mo’s Algorithm).

Square Root Decomposition
In this technique, we divide the array into blocks of √n elements. When we process a query from l to r, the idea is to store the pre-processed answer in the blocks array so when we get a block in a range of l to r we do not iterate through all the √n elements of the block rather we get the value from block array in O(1) time. This method reduces the time complexity by √n . Let us see this with an example of Range Sum Query.

Array Given

There are 10 elements in the array hence n = 10, now we divide this array into  blocks of 3 elements each and 4 elements in the last block.

Our block array will look like:

Block Ids are given by the formulae:

 Here remainder 0 refers to starting of new block.

The idea is to pre-process the array and store the sum of each blocks in blocks array with the above shown block ids. This helps when we process a query from l to r, if the range includes some blocks, we directly get their sum from blocks array in  time.

Let us see the code implementation of range sum of Square Root Decomposition:

#include<bits/stdc++.h>

using namespace std;
vector v(0);
int block_sum[1000]{0};
int blk_size; root n block sizes
//update operation
void update(int idx,int val){
int block_idx = idx/blk_size; // Gives the block ids
block_sum[block_idx] = block_sum[block_idx] + val – v[idx];
v[idx] = val;
}
//Query operation
int querry(int l,int r){
int sum = 0;
//case1
while(l<r && l%blk_size!=0 && l!=0){
sum+=v[l];
l++;
}
//case2
while(l+blk_size<=r){
sum +=block_sum[l/blk_size];
l+=blk_size;
}
//case3
while(l<=r){
sum +=v[l];
l++;
}
return sum;
}
void preprocess(int n){
int blk_idx = -1;
for(int i = 0;i<n;i++){
if(i%blk_size==0)
blk_idx++;
block_sum[blk_idx]+=v[i];
}
}
void printblock(){
for(int i = 0;i<blk_size;i++)
cout<<i<<” “<<block_sum[i]<<endl;

}
int main() { //Driver Program
int n; cin>>n;
blk_size = sqrt(n);
v.clear();
for(int i = 0;i>num;
v.push_back(num);
} preprocess(n);
printblock();
update(4,11);
printblock();

return 0;

}

Mo’s Algorithm

Now when we are done with the pre-requisite let us now discuss Mo’s Algorithm. This is exactly the same algorithm with a slight modification that we need to apply. Here we divide the array into same √n blocks and process the queries although here we first sort the queries in increasing order of their l values and if same then r values. Why we do such?

Let this be the previous query and for next query we need to move L to left, that means we have to again include those elements in the left. Moving L to left includes the elements and towards right excludes the elements and for the R pointer, moving it left excludes the elements and moving it right includes the elements. Hence, we see that we traverse the array with these two pointers which determines the time complexity of the program. Now if we minimise this movement, we can easily reduce the time complexity. Therefore, we need to sort the query list. Although this algorithm fails where there is any updation as updation distorts the sorted query list.

Now let us see the code implementation of Mo’s Algorithm.

#include<bits/stdc++.h>

using namespace std;
int block;
// Structure to represent a query range
struct Query
{
int L, R;
};
bool compare(Query x, Query y)
{
//Sort the blocks
if (x.L/block != y.L/block)
return x.L/block < y.L/block; return x.R < y.R; } void Result(int a[], int n, Query q[], int m) { // Find block size block = sqrt(n); sort(q, q + m, compare); int currL = 0, currR = 0; int currSum = 0; for (int i=0; i L)
{
currSum += a[currL-1];
currL–;
}
while (currR <= R) { currSum += a[currR]; currR++; } while (currR > R+1)
{
currSum -= a[currR-1];
currR–;
}
}
}

// Driver program
int main()
{
int n;
cin>>n;
int *arr = new int[n];
for(int i = 0;i>arr[i];
//int n = sizeof(a)/sizeof(a[0]);
int m;
cin>>m;
Query *q = new Query[m];
for(int i = 0;i>x>>y;
q[i].L = x;
q[i].R = y;
}
Result(arr, n, q, m);
return 0;
}

Time Complexity:  Which is far better than complexity. Hence in this way by dividing the array into blocks we can achieve a more efficient program than brute force.

Let us also see another example/application of Mo’s algorithm:

An array of positive integers a1, a2, …, an is given. Let us consider its arbitrary subarray al, al + 1…, ar, where 1 ≤ l ≤ r ≤ n. For every positive integer s denote by Ks the number of occurrences of s into the subarray. We call the power of the subarray the sum of products Ks·Ks·s for every positive integer s. The sum contains an only a finite number of nonzero summands as the number of different values in the array is indeed finite. You should calculate the power of t given subarrays. This is a popular code forces problem.

Solution:

Solution and Implementation of Mo’s algorithm here:

#include<bits/stdc++.h>

using namespace std;
int N, Q;
ll cur_ans, ans[200500];
int cnt[1000500];
int b_size;
int arr[200500];
pair queries[SS];
inline bool cmpF(const pair&a, const pair&b){
int xblock = a.first.first / b_size;
int yblock = b.first.first / b_size;
if (xblock != yblock){
return xblock < yblock;
}
return a.first.second < b.first.second;
}

inline void add(ll x){
cur_ans += x * 1ll * (cnt[x] << 1 | 1);
cnt[x]++;
}

inline void removeX(ll x){
cur_ans -= x * 1ll * ((cnt[x] – 1) << 1 | 1); cnt[x]–; } int main(){ cin>>N>>Q;
cur_ans = 0;

b_size = sqrt(N);
for (int i = 0; i < N; i++){
cin>>a[i];
}
for (int i = 0; i < Q; i++){
    scanf("%d %d", &queries[i].first.first, &queries[i].first.second);
    queries[i].first.first--; queries[i].first.second--;
    queries[i].second = i;
}
sort(queries, queries + Q, cmpF);
int m_left = 0, m_right = -1;
for (int i = 0; i < Q; i++){
    if (i == 0 || queries[i].first.first / b_size != queries[i - 1].first.first / b_size){
        memset(cnt, 0, sizeof(cnt));
        cur_ans = 0;
        for (int j = queries[i].first.first; j <= queries[i].first.second; j++){
            add(arr[j]);
        }
        m_left = queries[i].first.first, m_right = queries[i].first.second;
        ans[queries[i].second] = cur_ans;
    }
    else{
        int left = queries[i].first.first;
        int right = queries[i].first.second;
        while (m_right < right){
            m_right++;
            add(arr[m_right]);
        }
        while (m_right>right){
            removeX(arr[m_right]);
            m_right--;
        }
        while (m_left < left){
            removeX(arr[m_left]);
            m_left++;
        }
        while (m_left > left){
            m_left--;
            add(arr[m_left]);
        }

        ans[queries[i].second] = cur_ans;
    }
}
for (int i = 0; i < Q; i++){
    cout<<ans[i];
}
return 0;

}

Hence, we can see that Mo’s Algorithm gives us the power of reducing the time complexity by the square root of the original in offline query problems. This is a very handy tool in competitive programming as it makes the program more efficient. Hope this article helps an aspiring programmer and developer.

Comments

  1. Nice blog. It was very useful for me. I m happy I found this blog. Thank you for sharing with us.https://www.acte.in/java-training-in-pune

    ReplyDelete

Post a Comment

Popular posts from this blog

NT Part 6: Sieve of Eratosthenes(Details)

  About 2300 years ago from today, famous Greek mathematician Euclid proved that there are an infinite number of prime numbers. Since then people have been searching for these prime numbers. In 1849, one of the greatest mathematician of all time, Carl Fredrick Gauss, had identified almost all of the prime numbers within the first 3 hundred thousand whole numbers. In the age of computers, we can find large prime numbers in the blink of an eye. But to do that, we need to know a bit of programming and a 2000 year old algorithm. By the end of this tutorial, you will be able to figure out a solution on your own to Gauss’s problem. What is a Prime Number? A prime number is an integer number that is divisible by 1 and the number itself only. For example, 7 is divisible by 1 and 7 only. But 6 is not a prime number because 6 is be divisible by 2 and 3 as well. It is worth mentioning that 1 itself is not a prime number. Now if you are asked to determine if a number is a prime number, you can...

Binary Search & The Kahini!

  Suppose you are given an array of numbers and you need to find whether the number X is in the array. The straight forward approach to do that would be to run a loop and compare each number in the array with X. This is what the pseudo code would look like: function findNumber ( A , x ) for i = 0 to length of array A if A [ i ] == x return true return false For example, let’s assume you are given this array: [7, 14, 17, 21, 35, 60, 92, 121, 155] And, you were looking for the number 92. The loop would start checking each of the number in the array against 92 and would find after iterating through the array 7 times. The worst case time complexity of this approach would be O(n). However, whenever the array itself is in sorted order (like in this case where the numbers are increasing in value from left to right of the array), you can perform something that is much more efficient: binary search. The prerequisite of binary search is that the array should be sor...

NT Part 3: Playing with Modulars, and Euler Phi Function

  Quick stuff first, fast exponentiation in logarithm time. Let us calculate   in modular   in  . It uses binary expansion of  ,  and is very very straightforward. ll exp ( ll x, ll n ) { if ( n == 0 ) return 1 ; if ( n == 1 ) return x ; if ( n % 2 == 0 ) return exp ( ( x * x ) % mod,n / 2 ) ; if ( n % 2 == 1 ) return ( x * exp ( ( x * x ) % mod,n / 2 ) ) % mod ; } Now, let us talk about modular inverses. By using Extended Euclidean Algorithm, we can get the inverse of   modulo  . #include <iostream> int inv ( int a, int m ) { int temp = m, q, t, u = 0 , v = 1 ; if ( m == 1 ) return 0 ; while ( a > 1 ) { q = a / m ; t = m ; m = a % m ; a = t ; t = u ; u = v - q * u ; v = t ; } if ( v < 0 ) v + = temp ; return v ; } int main ( void ) { int a, m ; std :: cin ...