Skip to content
geeksforgeeks
  • Tutorials
    • Python
    • Java
    • Data Structures & Algorithms
    • ML & Data Science
    • Interview Corner
    • Programming Languages
    • Web Development
    • CS Subjects
    • DevOps And Linux
    • School Learning
    • Practice Coding Problems
  • Courses
    • DSA to Development
    • Get IBM Certification
    • Newly Launched!
      • Master Django Framework
      • Become AWS Certified
    • For Working Professionals
      • Interview 101: DSA & System Design
      • Data Science Training Program
      • JAVA Backend Development (Live)
      • DevOps Engineering (LIVE)
      • Data Structures & Algorithms in Python
    • For Students
      • Placement Preparation Course
      • Data Science (Live)
      • Data Structure & Algorithm-Self Paced (C++/JAVA)
      • Master Competitive Programming (Live)
      • Full Stack Development with React & Node JS (Live)
    • Full Stack Development
    • Data Science Program
    • All Courses
  • DSA
  • Practice Searching Algorithms
  • MCQs on Searching Algorithms
  • Tutorial on Searching Algorithms
  • Linear Search
  • Binary Search
  • Ternary Search
  • Jump Search
  • Sentinel Linear Search
  • Interpolation Search
  • Exponential Search
  • Fibonacci Search
  • Ubiquitous Binary Search
  • Linear Search Vs Binary Search
  • Interpolation Search Vs Binary Search
  • Binary Search Vs Ternary Search
  • Sentinel Linear Search Vs Linear Search
Open In App
Next Article:
Extended Mo's Algorithm with ≈ O(1) time complexity
Next article icon

Extended Mo's Algorithm with ≈ O(1) time complexity

Last Updated : 28 Feb, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

Given an array of n elements and q range queries (range sum in this article) with no updates, task is to answer these queries with efficient time and space complexity. The time complexity of a range query after applying square root decomposition comes out to be O(?n). This square-root factor can be decreased to a constant linear factor by applying square root decomposition on the block of the array which was decomposed earlier.
Prerequisite: Mo's Algorithm | Prefix Array

Approach : 
As we apply square root decomposition to the given array, querying a range-sum comes in O(?n) time. 
Here, calculate the sum of blocks which are in between the blocks under consideration(corner blocks), which takes O(?n) iterations.
Initial Array : 

Initial Array
Initial Array

Decomposition of array into blocks :  

Decomposition at level-1
Decomposition at level-1


And the calculation time for the sum on the starting block and ending block both takes O(?n) iterations. 

Which will leaves us per query time complexity of : 

= O(?n) + O(?n) + O(?n)
= 3 * O(?n)
~O(?n)


Here, we can reduce the runtime complexity of our query algorithm cleverly by calculating blockwise prefix sum and using it to calculate the sum accumulated in the blocks which lie between the blocks under consideration. Consider the code below : 

interblock_sum[x1][x2] = prefixData[x2 - 1] - prefixData[x1];


Time taken for calculation of above table is : 

= O(?n) * O(?n)  
~O(n)


NOTE : We haven't taken the sum of blocks x1 & x2 under consideration as they might be carrying partial data.
Prefix Array : 

Prefix Array
Prefix Array

Suppose we want to query for the sum for range from 4 to 11, we consider the sum between block 0 and block 2 (excluding the data contained in block 0 and block 1), which can be calculated using the sum in the green coloured blocks represented in the above image.
Sum between block 0 and block 2 = 42 - 12 = 30
For calculation of rest of the sum present in the yellow blocks, consider the prefix array at the decomposition level-2 and repeat the process again. 
Here, observe that we have reduced our time complexity per query significantly, though our runtime remains similar to our last approach :

Our new time complexity can be calculated as :  

= O(?n) + O(1) + O(?n)
= 2 * O(?n)
~O(?n)

Square-root Decomposition at Level-2 :  

Decomposition at level-2
Decomposition at level-2

Further, we apply square root decomposition again on every decomposed block retained from the previous decomposition. Now at this level, we have approximately ??n sub-blocks in each block which were decomposed at last level. So, we need to run a range query on these blocks only two times, one time for starting block and one time for ending block. 

Precalculation Time taken for level 2 decomposition : 
No of blocks at level 1 ~ ?n 
No of blocks at level 2 ~ ??n

Level-2 Decomposition Runtime of a level-1 decomposed block :  

= O(?n)

Overall runtime of level-2 decomposition over all blocks : 

= O(?n) * O(?n)
~O(n)

Now, we can query our level-2 decomposed blocks in O(??n) time. 
So, we have reduced our overall time complexity from O(?n) to O(??n)

Time complexity taken in querying edge blocks :  

= O(??n) + O(1) + O(??n)
= 2 * O(??n)
~O(??n)


Total Time complexity can be calculated as : 

= O(??n)+O(1)+O(??n)
= 2 * O(??n)
~O(??n)

Square-root Decomposition at Level-3 :  

Decomposition at level-3
Decomposition at level-3

Using this method we can decompose our array again and again recursively d times to reduce our time complexity to a factor of constant linearity.  

O(d * n1/(2^d)) ~ O(k), as d increases this factor converges to a constant linear term


The code presented below is a representation of triple square root decomposition where d = 3: 

O(q * d * n1/(2 ^ 3)) ~ O(q * k) ~ O(q) 


[where q represents number of range queries]

C++
// CPP code for offline queries in // approx constant time. #include<bits/stdc++.h> using namespace std;  int n1;  // Structure to store decomposed data typedef struct  {     vector<int> data;     vector<vector<int>> rdata;      int blocks;      int blk_sz;  }sqrtD;  vector<vector<sqrtD>> Sq3; vector<sqrtD> Sq2; sqrtD Sq1;  // Square root Decomposition of  // a given array sqrtD decompose(vector<int> arr) {     sqrtD sq;      int n = arr.size();     int blk_idx = -1;      sq.blk_sz = sqrt(n);      sq.data.resize((n/sq.blk_sz) + 1, 0);          // Calculation of data in blocks     for (int i = 0; i < n; i++)     {         if (i % sq.blk_sz == 0)         {             blk_idx++;         }         sq.data[blk_idx] += arr[i];     }          int blocks = blk_idx + 1;     sq.blocks = blocks;          // Calculation of prefix data     int prefixData[blocks];     prefixData[0] = sq.data[0];     for(int i = 1; i < blocks; i++)     {         prefixData[i] =            prefixData[i - 1] + sq.data[i];     }          sq.rdata.resize(blocks + 1,              vector<int>(blocks + 1));          // Calculation of data between blocks     for(int i = 0 ;i < blocks; i++)     {         for(int j = i + 1; j < blocks; j++)         {             sq.rdata[i][j] = sq.rdata[j][i] =             prefixData[j - 1] - prefixData[i];         }     }           return sq; }  // Square root Decomposition at level3 vector<vector<sqrtD>> tripleDecompose(sqrtD sq1,                       sqrtD sq2,vector<int> &arr) {     vector<vector<sqrtD>> sq(sq1.blocks,                       vector<sqrtD>(sq1.blocks));          int blk_idx1 = -1;          for(int i = 0; i < sq1.blocks; i++)     {         int blk_ldx1 = blk_idx1 + 1;         blk_idx1 = (i + 1) * sq1.blk_sz - 1;         blk_idx1 = min(blk_idx1,n1 - 1);           int blk_idx2 = blk_ldx1 - 1;                  for(int j = 0; j < sq2.blocks; ++j)         {             int blk_ldx2 = blk_idx2 + 1;             blk_idx2 = blk_ldx1 + (j + 1) *                         sq2.blk_sz - 1;             blk_idx2 = min(blk_idx2, blk_idx1);                      vector<int> ::iterator it1 =                          arr.begin() + blk_ldx2;             vector<int> ::iterator it2 =                          arr.begin() + blk_idx2 + 1;             vector<int> vec(it1, it2);             sq[i][j] = decompose(vec);              }     }     return sq;          }  // Square root Decomposition at level2 vector<sqrtD> doubleDecompose(sqrtD sq1,                                vector<int> &arr) {     vector<sqrtD> sq(sq1.blocks);     int blk_idx = -1;     for(int i = 0; i < sq1.blocks; i++)     {         int blk_ldx = blk_idx + 1;         blk_idx = (i + 1) * sq1.blk_sz - 1;         blk_idx = min(blk_idx, n1 - 1);         vector<int> ::iterator it1 =                      arr.begin() + blk_ldx;         vector<int> ::iterator it2 =                      arr.begin() + blk_idx + 1;         vector<int> vec(it1, it2);         sq[i] = decompose(vec);     }          return sq;      }  // Square root Decomposition at level1 void singleDecompose(vector<int> &arr) {     sqrtD sq1 = decompose(arr);     vector<sqrtD> sq2(sq1.blocks);      sq2 = doubleDecompose(sq1, arr);          vector<vector<sqrtD>> sq3(sq1.blocks,            vector<sqrtD>(sq2[0].blocks));                 sq3 = tripleDecompose(sq1, sq2[0],arr);              // ASSIGNMENT TO GLOBAL VARIABLES     Sq1 = sq1;     Sq2.resize(sq1.blocks);     Sq2 = sq2;     Sq3.resize(sq1.blocks,          vector<sqrtD>(sq2[0].blocks));     Sq3 = sq3; }  // Function for query at level 3 int queryLevel3(int start,int end, int main_blk,                 int sub_main_blk, vector<int> &arr) {     int blk_sz= Sq3[0][0].blk_sz;      // Element Indexing at level2 decomposition     int nstart = start - main_blk *          Sq1.blk_sz - sub_main_blk * Sq2[0].blk_sz;     int nend = end - main_blk *          Sq1.blk_sz - sub_main_blk * Sq2[0].blk_sz;      // Block indexing at level3 decomposition      int st_blk = nstart / blk_sz;     int en_blk = nend / blk_sz;          int answer =          Sq3[main_blk][sub_main_blk].rdata[st_blk][en_blk];           // If start and end point don't lie in same block      if(st_blk != en_blk)     {          int left = 0, en_idx = main_blk * Sq1.blk_sz +                       sub_main_blk * Sq2[0].blk_sz +                        (st_blk + 1) * blk_sz -1;              for(int i = start; i <= en_idx; i++)         {             left += arr[i];          }                  int right = 0, st_idx = main_blk * Sq1.blk_sz +                         sub_main_blk * Sq2[0].blk_sz +                        (en_blk) * blk_sz;                   for(int i = st_idx; i <= end; i++)         {             right += arr[i];          }                  answer += left;          answer += right;      }     else     {         for(int i = start; i <= end; i++)         {             answer += arr[i];     }  } return answer;      }  // Function for splitting query to level two int queryLevel2(int start, int end, int main_blk,                 vector<int> &arr) {     int blk_sz = Sq2[0].blk_sz;      // Element Indexing at level1 decomposition     int nstart = start - (main_blk * Sq1.blk_sz);     int nend = end - (main_blk * Sq1.blk_sz);      // Block indexing at level2 decomposition      int st_blk = nstart / blk_sz;     int en_blk = nend / blk_sz;          // Interblock data level2 decomposition      int answer = Sq2[main_blk].rdata[st_blk][en_blk];           if(st_blk == en_blk)     {         answer += queryLevel3(start, end, main_blk,                               st_blk, arr);     }      else      {          answer += queryLevel3(start, (main_blk *                   Sq1.blk_sz) + ((st_blk + 1) *                    blk_sz) - 1, main_blk, st_blk, arr);                  answer += queryLevel3((main_blk * Sq1.blk_sz) +                   (en_blk * blk_sz), end, main_blk, en_blk, arr);     }      return answer;      }  // Function to return answer according to query int Query(int start,int end,vector<int>& arr) {     int blk_sz = Sq1.blk_sz;     int st_blk = start / blk_sz;     int en_blk = end / blk_sz;          // Interblock data level1 decomposition          int answer = Sq1.rdata[st_blk][en_blk];       if(st_blk == en_blk)     {         answer += queryLevel2(start, end, st_blk, arr);     }      else      {          answer += queryLevel2(start, (st_blk + 1) *                    blk_sz - 1, st_blk, arr);         answer += queryLevel2(en_blk * blk_sz, end,                                en_blk, arr);     }          // returning final answer     return answer; }  // Driver code int main()  {          n1 = 16;          vector<int> arr = {7, 2, 3, 0, 5, 10, 3, 12,                         18, 1, 2, 3, 4, 5, 6, 7};      singleDecompose(arr);          int q = 5;     pair<int, int> query[q] = {{6, 10}, {7, 12},                   {4, 13}, {4, 11}, {12, 16}};          for(int i = 0; i < q; i++)     {         int a = query[i].first, b = query[i].second;         printf("%d\n", Query(a - 1, b - 1, arr));     }          return 0; } 
Java
import java.util.Arrays;  class sqrtD {     int[] data;     int[][] rdata;     int blocks;     int blk_sz;      // Constructor for sqrtD     sqrtD(int n) {         blk_sz = (int) Math.sqrt(n);         data = new int[(n / blk_sz) + 1];         Arrays.fill(data, 0);     } }  public class OfflineQueries {     static int n1;     static sqrtD Sq1;     static sqrtD[] Sq2;     static sqrtD[][] Sq3;      // Function to decompose an array using square root decomposition     static sqrtD decompose(int[] arr) {         int n = arr.length;         sqrtD sq = new sqrtD(n);         int blk_idx = -1;          // Calculation of data in blocks         for (int i = 0; i < n; i++) {             if (i % sq.blk_sz == 0) {                 blk_idx++;             }             sq.data[blk_idx] += arr[i];         }          int blocks = blk_idx + 1;         sq.blocks = blocks;          // Calculation of prefix data         int[] prefixData = new int[blocks];         prefixData[0] = sq.data[0];         for (int i = 1; i < blocks; i++) {             prefixData[i] = prefixData[i - 1] + sq.data[i];         }          sq.rdata = new int[blocks + 1][blocks + 1];          // Calculation of data between blocks         for (int i = 0; i < blocks; i++) {             for (int j = i + 1; j < blocks; j++) {                 sq.rdata[i][j] = sq.rdata[j][i] = prefixData[j - 1] - prefixData[i];             }         }          return sq;     }      // Function to perform triple decomposition     static sqrtD[][] tripleDecompose(sqrtD sq1, sqrtD sq2, int[] arr) {         sqrtD[][] sq = new sqrtD[sq1.blocks][sq2.blocks];          int blk_idx1 = -1;          for (int i = 0; i < sq1.blocks; i++) {             int blk_ldx1 = blk_idx1 + 1;             blk_idx1 = (i + 1) * sq1.blk_sz - 1;             blk_idx1 = Math.min(blk_idx1, n1 - 1);              int blk_idx2 = blk_ldx1 - 1;              for (int j = 0; j < sq2.blocks; ++j) {                 int blk_ldx2 = blk_idx2 + 1;                 blk_idx2 = blk_ldx1 + (j + 1) * sq2.blk_sz - 1;                 blk_idx2 = Math.min(blk_idx2, blk_idx1);                  int[] vec = Arrays.copyOfRange(arr, blk_ldx2, blk_idx2 + 1);                 sq[i][j] = decompose(vec);             }         }         return sq;     }      // Function to perform double decomposition     static sqrtD[] doubleDecompose(sqrtD sq1, int[] arr) {         sqrtD[] sq = new sqrtD[sq1.blocks];         int blk_idx = -1;         for (int i = 0; i < sq1.blocks; i++) {             int blk_ldx = blk_idx + 1;             blk_idx = (i + 1) * sq1.blk_sz - 1;             blk_idx = Math.min(blk_idx, n1 - 1);             int[] vec = Arrays.copyOfRange(arr, blk_ldx, blk_idx + 1);             sq[i] = decompose(vec);         }         return sq;     }      // Function to perform single decomposition     static void singleDecompose(int[] arr) {         Sq1 = decompose(arr);         Sq2 = doubleDecompose(Sq1, arr);          Sq3 = tripleDecompose(Sq1, Sq2[0], arr);     }      // Function to query at level 3     static int queryLevel3(int start, int end, int main_blk, int sub_main_blk, int[] arr) {         int blk_sz = Sq3[0][0].blk_sz;          // Element Indexing at level2 decomposition         int nstart = start - main_blk * Sq1.blk_sz - sub_main_blk * Sq2[0].blk_sz;         int nend = end - main_blk * Sq1.blk_sz - sub_main_blk * Sq2[0].blk_sz;          // Block indexing at level3 decomposition         int st_blk = nstart / blk_sz;         int en_blk = nend / blk_sz;          int answer = Sq3[main_blk][sub_main_blk].rdata[st_blk][en_blk];          // If start and end point don't lie in the same block         if (st_blk != en_blk) {             int left = 0, en_idx = main_blk * Sq1.blk_sz + sub_main_blk * Sq2[0].blk_sz + (st_blk + 1) * blk_sz - 1;              for (int i = start; i <= en_idx; i++) {                 left += arr[i];             }              int right = 0, st_idx = main_blk * Sq1.blk_sz + sub_main_blk * Sq2[0].blk_sz + (en_blk) * blk_sz;              for (int i = st_idx; i <= end; i++) {                 right += arr[i];             }              answer += left;             answer += right;         } else {             for (int i = start; i <= end; i++) {                 answer += arr[i];             }         }         return answer;     }      // Function to query at level 2     static int queryLevel2(int start, int end, int main_blk, int[] arr) {         int blk_sz = Sq2[0].blk_sz;          // Element Indexing at level1 decomposition         int nstart = start - (main_blk * Sq1.blk_sz);         int nend = end - (main_blk * Sq1.blk_sz);          // Block indexing at level2 decomposition         int st_blk = nstart / blk_sz;         int en_blk = nend / blk_sz;          // Interblock data level2 decomposition         int answer = Sq2[main_blk].rdata[st_blk][en_blk];          if (st_blk == en_blk) {             answer += queryLevel3(start, end, main_blk, st_blk, arr);         } else {             answer += queryLevel3(start, (main_blk * Sq1.blk_sz) + ((st_blk + 1) * blk_sz) - 1, main_blk, st_blk, arr);              answer += queryLevel3((main_blk * Sq1.blk_sz) + (en_blk * blk_sz), end, main_blk, en_blk, arr);         }         return answer;     }      // Function to return the answer according to the query     static int Query(int start, int end, int[] arr) {         int blk_sz = Sq1.blk_sz;         int st_blk = start / blk_sz;         int en_blk = end / blk_sz;          // Interblock data level1 decomposition         int answer = Sq1.rdata[st_blk][en_blk];          if (st_blk == en_blk) {             answer += queryLevel2(start, end, st_blk, arr);         } else {             answer += queryLevel2(start, (st_blk + 1) * blk_sz - 1, st_blk, arr);             answer += queryLevel2(en_blk * blk_sz, end, en_blk, arr);         }          // Returning the final answer         return answer;     }      // Driver code     public static void main(String[] args) {         n1 = 16;          int[] arr = {7, 2, 3, 0, 5, 10, 3, 12, 18, 1, 2, 3, 4, 5, 6, 7};          singleDecompose(arr);          int q = 5;         int[][] query = {{6, 10}, {7, 12}, {4, 13}, {4, 11}, {12, 16}};          for (int i = 0; i < q; i++) {             int a = query[i][0], b = query[i][1];             System.out.println(Query(a - 1, b - 1, arr));         }     } } 
Python3
import math  class sqrtD:     def __init__(self):         self.data = []            self.rdata = []           self.blocks = 0            self.blk_sz = 0     # Function to decompose an array into blocks and calculate block-wise and cumulative sums def decompose(arr):     sq = sqrtD()     n = len(arr)     blk_idx = -1     sq.blk_sz = int(math.sqrt(n))     sq.data = [0] * ((n // sq.blk_sz) + 1)      # Calculation of block-wise sums     for i in range(n):         if i % sq.blk_sz == 0:             blk_idx += 1         sq.data[blk_idx] += arr[i]      blocks = blk_idx + 1     sq.blocks = blocks      # Calculation of cumulative sums between blocks     prefix_data = [0] * blocks     prefix_data[0] = sq.data[0]      for i in range(1, blocks):         prefix_data[i] = prefix_data[i - 1] + sq.data[i]      sq.rdata = [[0] * (blocks + 1) for _ in range(blocks + 1)]      for i in range(blocks):         for j in range(i + 1, blocks):             sq.rdata[i][j] = sq.rdata[j][i] = prefix_data[j - 1] - prefix_data[i]      return sq  # Function to decompose two arrays at level 3 def triple_decompose(sq1, sq2, arr):     sq = [[None] * sq1.blocks for _ in range(sq1.blocks)]      blk_idx1 = -1      for i in range(sq1.blocks):         blk_ldx1 = blk_idx1 + 1         blk_idx1 = min((i + 1) * sq1.blk_sz - 1, n1 - 1)          blk_idx2 = blk_ldx1 - 1          for j in range(sq2.blocks):             blk_ldx2 = blk_idx2 + 1             blk_idx2 = min(blk_ldx1 + (j + 1) * sq2.blk_sz - 1, blk_idx1)              vec = arr[blk_ldx2:blk_idx2 + 1]             sq[i][j] = decompose(vec)      return sq  # Function to decompose one array at level 2 def double_decompose(sq1, arr):     sq = [None] * sq1.blocks     blk_idx = -1      for i in range(sq1.blocks):         blk_ldx = blk_idx + 1         blk_idx = min((i + 1) * sq1.blk_sz - 1, n1 - 1)          vec = arr[blk_ldx:blk_idx + 1]         sq[i] = decompose(vec)      return sq  # Function to decompose one array at level 1 def single_decompose(arr):     global Sq1, Sq2, Sq3     sq1 = decompose(arr)     sq2 = double_decompose(sq1, arr)      sq3 = triple_decompose(sq1, sq2[0], arr)      Sq1 = sq1     Sq2 = sq2     Sq3 = sq3  # Function for querying at level 3 def query_level3(start, end, main_blk, sub_main_blk, arr):     blk_sz = Sq3[0][0].blk_sz     nstart = start - main_blk * Sq1.blk_sz - sub_main_blk * Sq2[0].blk_sz     nend = end - main_blk * Sq1.blk_sz - sub_main_blk * Sq2[0].blk_sz     st_blk = nstart // blk_sz     en_blk = nend // blk_sz      answer = Sq3[main_blk][sub_main_blk].rdata[st_blk][en_blk]      if st_blk != en_blk:         left = 0         en_idx = main_blk * Sq1.blk_sz + sub_main_blk * Sq2[0].blk_sz + (st_blk + 1) * blk_sz - 1         for i in range(start, en_idx + 1):             left += arr[i]          right = 0         st_idx = main_blk * Sq1.blk_sz + sub_main_blk * Sq2[0].blk_sz + (en_blk) * blk_sz         for i in range(st_idx, end + 1):             right += arr[i]          answer += left         answer += right     else:         for i in range(start, end + 1):             answer += arr[i]      return answer  # Function for querying at level 2 def query_level2(start, end, main_blk, arr):     blk_sz = Sq2[0].blk_sz     nstart = start - main_blk * Sq1.blk_sz     nend = end - main_blk * Sq1.blk_sz     st_blk = nstart // blk_sz     en_blk = nend // blk_sz     answer = Sq2[main_blk].rdata[st_blk][en_blk]      if st_blk == en_blk:         answer += query_level3(start, end, main_blk, st_blk, arr)     else:         answer += query_level3(start, (main_blk * Sq1.blk_sz) + ((st_blk + 1) * blk_sz) - 1, main_blk, st_blk, arr)         answer += query_level3((main_blk * Sq1.blk_sz) + (en_blk * blk_sz), end, main_blk, en_blk, arr)      return answer  # Function to handle queries def Query(start, end, arr):     blk_sz = Sq1.blk_sz     st_blk = start // blk_sz     en_blk = end // blk_sz     answer = Sq1.rdata[st_blk][en_blk]      if st_blk == en_blk:         answer += query_level2(start, end, st_blk, arr)     else:         answer += query_level2(start, (st_blk + 1) * blk_sz - 1, st_blk, arr)         answer += query_level2(en_blk * blk_sz, end, en_blk, arr)      return answer  # Driver code n1 = 16 arr = [7, 2, 3, 0, 5, 10, 3, 12, 18, 1, 2, 3, 4, 5, 6, 7] single_decompose(arr)  q = 5 queries = [(6, 10), (7, 12), (4, 13), (4, 11), (12, 16)]  for query in queries:     a, b = query     print(Query(a - 1, b - 1, arr)) 
C#
using System; using System.Collections.Generic;  public class MainClass {     // Global variable to store the size of blocks     public static int n1;      // Class to represent a block in the decomposed data     // structure     public class SqrtD {         // Data stored in the block         public List<int> data;          // Prefix sum data between blocks         public List<List<int> > rdata;          // Number of blocks         public int blocks;          // Size of each block         public int blk_sz;          // Constructor to initialize lists         public SqrtD()         {             data = new List<int>();             rdata = new List<List<int> >();         }     }      // Global variables to store decomposed data structure     public static List<List<SqrtD> > Sq3;     public static List<SqrtD> Sq2;     public static SqrtD Sq1;      // Function to decompose the input array into blocks     public static SqrtD Decompose(List<int> arr)     {         SqrtD sq = new SqrtD();         int n = arr.Count;         int blk_idx = -1;         sq.blk_sz = (int)Math.Sqrt(n);         sq.data = new List<int>((n / sq.blk_sz) + 1);         for (int i = 0; i < sq.data.Capacity; i++) {             sq.data.Add(0);         }          for (int i = 0; i < n; i++) {             if (i % sq.blk_sz == 0) {                 blk_idx++;             }             sq.data[blk_idx] += arr[i];         }          int blocks = blk_idx + 1;         sq.blocks = blocks;          int[] prefixData = new int[blocks];         prefixData[0] = sq.data[0];         for (int i = 1; i < blocks; i++) {             prefixData[i] = prefixData[i - 1] + sq.data[i];         }          sq.rdata = new List<List<int> >();         for (int i = 0; i <= blocks; i++) {             sq.rdata.Add(                 new List<int>(new int[blocks + 1]));         }          for (int i = 0; i < blocks; i++) {             for (int j = i + 1; j < blocks; j++) {                 sq.rdata[i][j] = sq.rdata[j][i]                     = prefixData[j - 1] - prefixData[i];             }         }          return sq;     }      // Function to decompose the data structure into triple     // levels     public static List<List<SqrtD> >     TripleDecompose(SqrtD sq1, SqrtD sq2, List<int> arr)     {         List<List<SqrtD> > sq             = new List<List<SqrtD> >(sq1.blocks);         for (int i = 0; i < sq1.blocks; i++) {             sq.Add(new List<SqrtD>(new SqrtD[sq1.blocks]));         }          int blk_idx1 = -1;          for (int i = 0; i < sq1.blocks; i++) {             int blk_ldx1 = blk_idx1 + 1;             blk_idx1 = (i + 1) * sq1.blk_sz - 1;             blk_idx1 = Math.Min(blk_idx1, n1 - 1);              int blk_idx2 = blk_ldx1 - 1;              for (int j = 0; j < sq2.blocks; ++j) {                 int blk_ldx2 = blk_idx2 + 1;                 blk_idx2                     = blk_ldx1 + (j + 1) * sq2.blk_sz - 1;                 blk_idx2 = Math.Min(blk_idx2, blk_idx1);                  List<int> vec = new List<int>(arr.GetRange(                     blk_ldx2, blk_idx2 - blk_ldx2 + 1));                 sq[i][j] = Decompose(vec);             }         }         return sq;     }      // Function to decompose the data structure into double     // levels     public static List<SqrtD> DoubleDecompose(SqrtD sq1,                                               List<int> arr)     {         List<SqrtD> sq = new List<SqrtD>(sq1.blocks);         int blk_idx = -1;         for (int i = 0; i < sq1.blocks; i++) {             int blk_ldx = blk_idx + 1;             blk_idx = (i + 1) * sq1.blk_sz - 1;             blk_idx = Math.Min(blk_idx, n1 - 1);             List<int> vec = new List<int>(arr.GetRange(                 blk_ldx, blk_idx - blk_ldx + 1));             sq.Add(Decompose(vec));         }          return sq;     }      // Function to decompose the data structure into single     // level     public static void SingleDecompose(List<int> arr)     {         Sq1 = Decompose(arr);         Sq2 = DoubleDecompose(Sq1, arr);         Sq3 = TripleDecompose(Sq1, Sq2[0], arr);     }      // Function to handle queries at level 3 of     // decomposition     public static int QueryLevel3(int start, int end,                                   int main_blk,                                   int sub_main_blk,                                   List<int> arr)     {         int blk_sz = Sq3[0][0].blk_sz;          int nstart = start - main_blk * Sq1.blk_sz                      - sub_main_blk * Sq2[0].blk_sz;         int nend = end - main_blk * Sq1.blk_sz                    - sub_main_blk * Sq2[0].blk_sz;          int st_blk = nstart / blk_sz;         int en_blk = nend / blk_sz;          int answer = Sq3[main_blk][sub_main_blk]                          .rdata[st_blk][en_blk];          if (st_blk != en_blk) {             int left = 0, en_idx                           = main_blk * Sq1.blk_sz                             + sub_main_blk * Sq2[0].blk_sz                             + (st_blk + 1) * blk_sz - 1;              for (int i = start; i <= en_idx; i++) {                 left += arr[i];             }              int right = 0, st_idx                            = main_blk * Sq1.blk_sz                              + sub_main_blk * Sq2[0].blk_sz                              + (en_blk)*blk_sz;              for (int i = st_idx; i <= end; i++) {                 right += arr[i];             }              answer += left;             answer += right;         }         else {             for (int i = start; i <= end; i++) {                 answer += arr[i];             }         }         return answer;     }      // Function to handle queries at level 2 of     // decomposition     public static int QueryLevel2(int start, int end,                                   int main_blk,                                   List<int> arr)     {         int blk_sz = Sq2[0].blk_sz;          int nstart = start - (main_blk * Sq1.blk_sz);         int nend = end - (main_blk * Sq1.blk_sz);          int st_blk = nstart / blk_sz;         int en_blk = nend / blk_sz;          int answer = Sq2[main_blk].rdata[st_blk][en_blk];          if (st_blk == en_blk) {             answer += QueryLevel3(start, end, main_blk,                                   st_blk, arr);         }         else {             answer += QueryLevel3(                 start,                 (main_blk * Sq1.blk_sz)                     + ((st_blk + 1) * blk_sz) - 1,                 main_blk, st_blk, arr);             answer += QueryLevel3(                 (main_blk * Sq1.blk_sz) + (en_blk * blk_sz),                 end, main_blk, en_blk, arr);         }         return answer;     }      // Function to handle queries at level 1 of     // decomposition     public static int Query(int start, int end,                             List<int> arr)     {         int blk_sz = Sq1.blk_sz;         int st_blk = start / blk_sz;         int en_blk = end / blk_sz;          int answer = Sq1.rdata[st_blk][en_blk];          if (st_blk == en_blk) {             answer += QueryLevel2(start, end, st_blk, arr);         }         else {             answer += QueryLevel2(start,                                   (st_blk + 1) * blk_sz - 1,                                   st_blk, arr);             answer += QueryLevel2(en_blk * blk_sz, end,                                   en_blk, arr);         }          return answer;     }      public static void Main(string[] args)     {         n1 = 16;         List<int> arr             = new List<int>{ 7,  2, 3, 0, 5, 10, 3, 12,                              18, 1, 2, 3, 4, 5,  6, 7 };          SingleDecompose(arr);          int q = 5;         Tuple<int, int>[] query             = { Tuple.Create(6, 10), Tuple.Create(7, 12),                 Tuple.Create(4, 13), Tuple.Create(4, 11),                 Tuple.Create(12, 16) };          for (int i = 0; i < q; i++) {             int a = query[i].Item1, b = query[i].Item2;             Console.WriteLine(Query(a - 1, b - 1, arr));         }     } } 
JavaScript
// Structure to store decomposed data class sqrtD {     constructor() {         this.data = [];         this.rdata = [];         this.blocks = 0;         this.blk_sz = 0;     } }  let Sq1 = new sqrtD(); let Sq2 = []; let Sq3 = [];  // Square root Decomposition of a given array function decompose(arr) {     const sq = new sqrtD();     const n = arr.length;     let blk_idx = -1;     sq.blk_sz = Math.floor(Math.sqrt(n));     sq.data = Array(Math.ceil(n / sq.blk_sz)).fill(0);      // Calculation of data in blocks     for (let i = 0; i < n; i++) {         if (i % sq.blk_sz === 0) {             blk_idx++;         }         sq.data[blk_idx] += arr[i];     }      const blocks = blk_idx + 1;     sq.blocks = blocks;      // Calculation of prefix data     const prefixData = new Array(blocks).fill(0);     prefixData[0] = sq.data[0];     for (let i = 1; i < blocks; i++) {         prefixData[i] = prefixData[i - 1] + sq.data[i];     }      sq.rdata = new Array(blocks + 1).fill().map(() => new Array(blocks + 1).fill(0));      // Calculation of data between blocks     for (let i = 0; i < blocks; i++) {         for (let j = i + 1; j < blocks; j++) {             sq.rdata[i][j] = sq.rdata[j][i] = prefixData[j - 1] - prefixData[i];         }     }      return sq; }  // Square root Decomposition at level3 function tripleDecompose(sq1, sq2, arr) {     const sq = new Array(sq1.blocks).fill().map(() => new Array(sq2.blocks));      let blk_idx1 = -1;      for (let i = 0; i < sq1.blocks; i++) {         let blk_ldx1 = blk_idx1 + 1;         blk_idx1 = (i + 1) * sq1.blk_sz - 1;         blk_idx1 = Math.min(blk_idx1, arr.length - 1);          let blk_idx2 = blk_ldx1 - 1;          for (let j = 0; j < sq2.blocks; ++j) {             let blk_ldx2 = blk_idx2 + 1;             blk_idx2 = blk_ldx1 + (j + 1) * sq2.blk_sz - 1;             blk_idx2 = Math.min(blk_idx2, blk_idx1);              const vec = arr.slice(blk_ldx2, blk_idx2 + 1);             sq[i][j] = decompose(vec);         }     }     return sq; }  // Square root Decomposition at level2 function doubleDecompose(sq1, arr) {     const sq = [];      let blk_idx = -1;     for (let i = 0; i < sq1.blocks; i++) {         let blk_ldx = blk_idx + 1;         blk_idx = (i + 1) * sq1.blk_sz - 1;         blk_idx = Math.min(blk_idx, arr.length - 1);          const vec = arr.slice(blk_ldx, blk_idx + 1);         sq.push(decompose(vec));     }      return sq; }  // Square root Decomposition at level1 function singleDecompose(arr) {     Sq1 = decompose(arr);     Sq2 = doubleDecompose(Sq1, arr);     Sq3 = tripleDecompose(Sq1, Sq2[0], arr); }  // Function for query at level 3 function queryLevel3(start, end, main_blk, sub_main_blk, arr) {     const blk_sz = Sq3[0][0].blk_sz;      // Element Indexing at level2 decomposition     const nstart = start - main_blk * Sq1.blk_sz - sub_main_blk * Sq2[0].blk_sz;     const nend = end - main_blk * Sq1.blk_sz - sub_main_blk * Sq2[0].blk_sz;      // Block indexing at level3 decomposition     const st_blk = Math.floor(nstart / blk_sz);     const en_blk = Math.floor(nend / blk_sz);      let answer = Sq3[main_blk][sub_main_blk].rdata[st_blk][en_blk];      // If start and end point don't lie in same block     if (st_blk !== en_blk) {         let left = 0;         let en_idx = main_blk * Sq1.blk_sz + sub_main_blk * Sq2[0].blk_sz + (st_blk + 1) * blk_sz - 1;          for (let i = start; i <= en_idx; i++) {             left += arr[i];         }          let right = 0;         let st_idx = main_blk * Sq1.blk_sz + sub_main_blk * Sq2[0].blk_sz + en_blk * blk_sz;          for (let i = st_idx; i <= end; i++) {             right += arr[i];         }          answer += left;         answer += right;     } else {         for (let i = start; i <= end; i++) {             answer += arr[i];         }     }     return answer; }  // Function for splitting query to level two function queryLevel2(start, end, main_blk, arr) {     const blk_sz = Sq2[0].blk_sz;      // Element Indexing at level1 decomposition     const nstart = start - (main_blk * Sq1.blk_sz);     const nend = end - (main_blk * Sq1.blk_sz);      // Block indexing at level2 decomposition     const st_blk = Math.floor(nstart / blk_sz);     const en_blk = Math.floor(nend / blk_sz);      // Interblock data level2 decomposition     let answer = Sq2[main_blk].rdata[st_blk][en_blk];      if (st_blk === en_blk) {         answer += queryLevel3(start, end, main_blk, st_blk, arr);     } else {         answer += queryLevel3(start, main_blk * Sq1.blk_sz + (st_blk + 1) * blk_sz - 1, main_blk, st_blk, arr);         answer += queryLevel3(main_blk * Sq1.blk_sz + en_blk * blk_sz, end, main_blk, en_blk, arr);     }     return answer; }  // Function to return answer according to query function Query(start, end, arr) {     const blk_sz = Sq1.blk_sz;     const st_blk = Math.floor(start / blk_sz);     const en_blk = Math.floor(end / blk_sz);      // Interblock data level1 decomposition     let answer = Sq1.rdata[st_blk][en_blk];      if (st_blk === en_blk) {         answer += queryLevel2(start, end, st_blk, arr);     } else {         answer += queryLevel2(start, (st_blk + 1) * blk_sz - 1, st_blk, arr);         answer += queryLevel2(en_blk * blk_sz, end, en_blk, arr);     }      // returning final answer     return answer; }  // Driver code function main() {     const n1 = 16;     const arr = [7, 2, 3, 0, 5, 10, 3, 12, 18, 1, 2, 3, 4, 5, 6, 7];      singleDecompose(arr);      const q = 5;     const query = [[6, 10], [7, 12], [4, 13], [4, 11], [12, 16]];      for (let i = 0; i < q; i++) {         const [a, b] = query[i];         console.log(Query(a - 1, b - 1, arr));     } }  main(); 

Output
44 39 58 51 25

Time Complexity : O(q * d * n1/(2^3)) ? O(q * k) ? O(q) 
Auxiliary Space : O(k * n) ? O(n) 
Note : This article is to only explain the method of decomposing the square root to further decomposition.
 


Next Article
Extended Mo's Algorithm with ≈ O(1) time complexity

H

Harshit Saini
Improve
Article Tags :
  • Misc
  • Algorithms
  • Searching
  • Advanced Data Structure
  • Competitive Programming
  • DSA
  • array-range-queries
  • cpp-vector
Practice Tags :
  • Advanced Data Structure
  • Algorithms
  • Misc
  • Searching

Similar Reads

    What does Constant Time Complexity or Big O(1) mean?
    Big O notation is a concept, in computer science and mathematics that allows us to analyse and describe the efficiency of algorithms for worst cases. It provides a way to measure how the runtime of an algorithm or function changes as the input size grows. In this article we'll explore the idea of O(
    5 min read
    Types of Asymptotic Notations in Complexity Analysis of Algorithms
    We have discussed Asymptotic Analysis, and Worst, Average, and Best Cases of Algorithms. The main idea of asymptotic analysis is to have a measure of the efficiency of algorithms that don't depend on machine-specific constants and don't require algorithms to be implemented and time taken by programs
    8 min read
    How to Analyse Loops for Complexity Analysis of Algorithms
    We have discussed Asymptotic Analysis, Worst, Average and Best Cases and Asymptotic Notations in previous posts. In this post, an analysis of iterative programs with simple examples is discussed. The analysis of loops for the complexity analysis of algorithms involves finding the number of operation
    15+ min read
    Time Complexity of Loop with Powers
    What is the time complexity of the below function?C++void fun(int n, int k) { for (int i = 1; i <= n; i++) { int p = pow(i, k); for (int j = 1; j <= p; j++) { // Some O(1) work } } } // This code is contributed by Shubham SinghCvoid fun(int n, int k) { for (int i = 1; i <= n; i++) { int p =
    2 min read
    Complete Guide On Complexity Analysis - Data Structure and Algorithms Tutorial
    Complexity analysis is defined as a technique to characterise the time taken by an algorithm with respect to input size (independent from the machine, language and compiler). It is used for evaluating the variations of execution time on different algorithms. What is the need for Complexity Analysis?
    15+ min read
geeksforgeeks-footer-logo
Corporate & Communications Address:
A-143, 7th Floor, Sovereign Corporate Tower, Sector- 136, Noida, Uttar Pradesh (201305)
Registered Address:
K 061, Tower K, Gulshan Vivante Apartment, Sector 137, Noida, Gautam Buddh Nagar, Uttar Pradesh, 201305
GFG App on Play Store GFG App on App Store
Advertise with us
  • Company
  • About Us
  • Legal
  • Privacy Policy
  • In Media
  • Contact Us
  • Advertise with us
  • GFG Corporate Solution
  • Placement Training Program
  • Languages
  • Python
  • Java
  • C++
  • PHP
  • GoLang
  • SQL
  • R Language
  • Android Tutorial
  • Tutorials Archive
  • DSA
  • Data Structures
  • Algorithms
  • DSA for Beginners
  • Basic DSA Problems
  • DSA Roadmap
  • Top 100 DSA Interview Problems
  • DSA Roadmap by Sandeep Jain
  • All Cheat Sheets
  • Data Science & ML
  • Data Science With Python
  • Data Science For Beginner
  • Machine Learning
  • ML Maths
  • Data Visualisation
  • Pandas
  • NumPy
  • NLP
  • Deep Learning
  • Web Technologies
  • HTML
  • CSS
  • JavaScript
  • TypeScript
  • ReactJS
  • NextJS
  • Bootstrap
  • Web Design
  • Python Tutorial
  • Python Programming Examples
  • Python Projects
  • Python Tkinter
  • Python Web Scraping
  • OpenCV Tutorial
  • Python Interview Question
  • Django
  • Computer Science
  • Operating Systems
  • Computer Network
  • Database Management System
  • Software Engineering
  • Digital Logic Design
  • Engineering Maths
  • Software Development
  • Software Testing
  • DevOps
  • Git
  • Linux
  • AWS
  • Docker
  • Kubernetes
  • Azure
  • GCP
  • DevOps Roadmap
  • System Design
  • High Level Design
  • Low Level Design
  • UML Diagrams
  • Interview Guide
  • Design Patterns
  • OOAD
  • System Design Bootcamp
  • Interview Questions
  • Inteview Preparation
  • Competitive Programming
  • Top DS or Algo for CP
  • Company-Wise Recruitment Process
  • Company-Wise Preparation
  • Aptitude Preparation
  • Puzzles
  • School Subjects
  • Mathematics
  • Physics
  • Chemistry
  • Biology
  • Social Science
  • English Grammar
  • Commerce
  • World GK
  • GeeksforGeeks Videos
  • DSA
  • Python
  • Java
  • C++
  • Web Development
  • Data Science
  • CS Subjects
@GeeksforGeeks, Sanchhaya Education Private Limited, All rights reserved
We use cookies to ensure you have the best browsing experience on our website. By using our site, you acknowledge that you have read and understood our Cookie Policy & Privacy Policy
Lightbox
Improvement
Suggest Changes
Help us improve. Share your suggestions to enhance the article. Contribute your expertise and make a difference in the GeeksforGeeks portal.
geeksforgeeks-suggest-icon
Create Improvement
Enhance the article with your expertise. Contribute to the GeeksforGeeks community and help create better learning resources for all.
geeksforgeeks-improvement-icon
Suggest Changes
min 4 words, max Words Limit:1000

Thank You!

Your suggestions are valuable to us.

What kind of Experience do you want to share?

Interview Experiences
Admission Experiences
Career Journeys
Work Experiences
Campus Experiences
Competitive Exam Experiences