Number of elements greater than K in the range L to R using Fenwick Tree (offline queries)


In the field of computer science, we have to process large datasets which includes query selection and update operations. It is a challenging task for the developers to execute these operations in real-time with less time complexity.

Using Fenwick tree is an efficient way to tackle these range-based query problems.

Fenwick Tree is a data structure which updates elements and calculates the prefix sums of the numbers in a table efficiently. It is also known as Binary Indexed Tree.

In this article, we will discuss how to use Fenwick Trees for finding the number of elements greater than K in the range L to R.

Input Output Scenarios

Suppose you have an array of N elements and you want to find the number of elements within the array greater than K in the range L to R.

Input: 
arr = {1, 15, 12, 13, 6, 18, 14, 10, 23, 41, 16, 9}
N = 11, K = 17, L = 1, R = 8
Output: 
2

What are Offline Queries?

Offline queries are such query operations which are performed on datasets which are predetermined. In other words, these operations are only performed on those datasets for which there are no further modifications while the query is being processed.

Using Fenwick Tree

Here, we will use a Fenwick tree which has vectors at each of its node containing all the elements of the array in an order. Then, we use binary search for answering each query and counting the number of elements.

  • Create two functions, updateTree() and total() which are used to update and retrieve the prefix sums in the BIT respectively.

  • Next, create another function which will count the elements that are greater than ‘K’ in the range L to R. This function takes input values of ‘arr’, ‘N’, ‘L’, ‘R’ and ‘K’.

  • The count is calculated by subtracting the cumulative sum of K from cumulative sum of the maximum range N.

In order to exclude the elements which are outside the range, it subtracts the cumulative sum of L-1 (in case L is greater than 1).

Example

#include <iostream>
#include <vector>
using namespace std;

// Updating the Fenwick Tree
void updateTree(vector<int>& BIT, int index, int value) {
   while (index < BIT.size()) {
      BIT[index] += value;
      index += index & -index;
   }
}

int total(vector<int>& BIT, int index) {
   int result = 0;
   while (index > 0) {
      result += BIT[index];
      index -= index & -index;
   }
   return result;
}

// Counting the number of elements
int elementsGreaterThanK(vector<int>& arr, int N, int K, int L, int R) {
   vector<int> BIT(N+1, 0);

   for (int i = L; i <= R; i++) {
      updateTree(BIT, arr[i], 1);
   }

   int count = total(BIT, N) - total(BIT, K);
   if (L > 1) {
      count -= total(BIT, L-1);
   }
   return count;
}

int main() {
   vector<int> arr = {0, 5, 2, 3, 6, 8, 7, 1, 8, 4, 6, 9};
   int N = 11; // Total range of values
   int K = 4; // Highest value
   int L = 1; // Start index of the range
   int R = 8; // End index of the range

   int count = elementsGreaterThanK(arr, N, K, L, R);
   cout << "Number of elements greater than " << K << " in the range [" << L << ", " << R << "]: " << count << endl;

   return 0;
}

Output

Number of elements greater than 4 in the range [1, 8]: 5

Alternative Approach

Here, we will create a separate vector to store the queries. Each query consists of two variables, index and val. The index is used to store the position in the array, while val is used to store the value of element at that index. This approach enables the developers to carry out various query operations. Also, we calculate the number of elements which are greater than K for each query using the BIT.

Example

#include <iostream>
#include <vector>
using namespace std;
struct Query {
   int index;
   int val;
};

void updateTree(vector<int>& BIT, int index, int value) {
   while (index < BIT.size()) {
      BIT[index] += value;
      index += index & -index;
   }
}

int total(vector<int>& BIT, int index) {
   int result = 0;
   while (index > 0) {
      result += BIT[index];
      index -= index & -index;
   }
   return result;
}

vector<int> elementsGreaterThanK(vector<int>& arr, vector<Query>& queries, int K) {
   int N = arr.size();
   vector<int> BIT(N+1, 0);
   vector<int> result;

   for (int i = 0; i < N; i++) {
      updateTree(BIT, i+1, (arr[i] > K) ? 1 : 0);
   }

   for (auto query : queries) {
      if (query.val > K) {
         result.push_back(total(BIT, query.index));
      }
      else {
         result.push_back(0);
      }
   }

   return result;
}

int main() {
   vector<int> arr = {3, 14, 32, 7, 11, 5, 8, 6, 16, 2, 15};
   vector<Query> queries = {{2, 4}, {5, 3}, {3, 6}, {0, 3}};
   int K = 2;

   vector<int> counts = elementsGreaterThanK(arr, queries, K);

   for (int count : counts) {
      cout << "Number of elements greater than " << K << ": " << count << endl;
   }

   return 0;
}

Output

Number of elements greater than 2: 2
Number of elements greater than 2: 5
Number of elements greater than 2: 3
Number of elements greater than 2: 0

Conclusion

We can simply iterate through the array from index L to R and incremate by 1 each time the array element is greater than K in order to find the answers for each query. However, to decrease the time complexity, we use Fenwick trees for such type of query operations. Instead of Fenwick tree, we can also use Segment Trees and Sparse Tables for conducting such query operations.

Updated on: 12-Jul-2023

181 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements