Open In App

Queries to count array elements from a given range having a single set bit

Last Updated : 21 May, 2024
Comments
Improve
Suggest changes
4 Likes
Like
Report

Given an array arr[] consisting of N integers and a 2D array Q[][] consisting of queries of the following two types:

  • 1 L R: Print the count of numbers from the range [L, R] having only a single set bit.
  • 2 X V: Update the array element at Xth index with V.

Examples:

Input: arr[] = { 12, 11, 16, 2, 32 }, Q[][] = { { 1, 0, 2 }, { 2, 4, 24 }, { 1, 1, 4 } }
Output: 1 2
Explanation:

  • Query 1: Print the count of array elements with only a single bit in the range of indices [0, 2], i.e. {16}.
  • Query 2: Set arr[4] = 24. Therefore, the array arr[] modifies to {12, 11, 16, 24, 5}.
  • Query 3: Print the count of array elements with only a single bit in the range of indices [1, 4], i.e. {16, 32}.

Input: arr[] = { 2, 1, 101, 11, 4 }, Q[][] = { { 1, 2, 4 }, { 2, 4, 12 }, { 1, 2, 4 } }
Output: 1 0
Explanation:

  • Query 1: Print the count of array elements with only a single bit in the range of indices [2, 4], i.e. {4}.
  • Query 2: Set arr[4] = 24, which modifies the array to arr[] = { 2, 1, 101, 11, 12}.
  • Query 3: Print the count of array elements with only a single bit in the range of indices [2, 4]. But no array elements from the given range of indices contains only a single bit.

Naive Approach: The simplest approach is to traverse the array over the range of indices [L, R] for each query and for each element, check if it has exactly one set bit or not. Increment count for every array element for which it is found to be true. After traversing the entire range, print the value of count. For queries of type 2, simply arr[X] = V
Time Complexity: O(N * Q * log(N))
Auxiliary Space: O(1)

Efficient Approach: The above approach can be optimized using a Segment Tree. Follow the steps below to solve the problem:

  • Define a function, check(S) to check if integer contains only one set bit.
  • Initialize 3 variables: ss, se, and si to store the starting point of the current segment, ending point of the current segment, and current node value in the segment tree respectively.
  • Define a function, say build_seg(ss, se, si), to build a segment tree Similar to the sum segment tree, each node storing the count of elements with only a single bit in its subtree:
    • If ss == se, tree[s[i]] = check (arr[ss] )
    • Otherwise, traverse the left subtree and right subtree.
    • Now, update the current node as tree[si] = tree[2 * si + 1]+ tree[2 * si + 2].
  • Define a function, say update( ss, se, si, X, V), to point update a value in the array arr[]:
    • If current node is leaf node, i.e ss == se then update the tree[si] = check(V).
    • Otherwise, search for the Xth index i.e if X ? (ss + se) / 2 then Traverse to the left subtree i.e update(ss, mid, 2 * si + 1, X, V). Otherwise, traverse to the right subtree i.e update(mid + 1, se, 2 * si + 2, X, V).
    • Update the current node.
  • Define a function say query(ss, se, si, L, R) to count numbers having only a single bit in the range [L, R]:
    • Check if the current segment [L, R] does not intersect with [ss, se] then return 0.
    • Otherwise, if ss >= L and se ? R then return tree[si].
    • If none of the above conditions satisfies then return query(ss, mid, L, R, 2 * si + 1)+query(mid + 1, se, L, R, 2 * si + 2).
  • For query of type { 1, L, R }, print the query(0, N-1, 0, L, R).
  • For query of type { 2, X, V }, update the value in the tree, update(0, N-1, 0, X, V).

Below is the implementation of the above approach:

C++
Java Python C# JavaScript

Output
1 2 

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

Approach using Fenwick Tree

To optimize the process of handling the two types of queries, we can utilize a Fenwick Tree (Binary Indexed Tree, BIT). This tree structure is especially effective for cumulative frequency tables and can efficiently update elements and query prefix sums. In this scenario, we maintain a BIT that holds counts of numbers with a single set bit. This allows us to achieve faster updates and range queries, reducing complexity compared to a Segment Tree.

Why Use Fenwick Tree for this Problem:

  • Faster Updates: The Fenwick Tree provides a way to update an element in logarithmic time.
  • Efficient Range Queries: It allows sum queries (or in our case, count queries) over a range in logarithmic time after an O(N log N) preprocessing time.
  • Space Efficiency: It uses space proportional to the array size, which is optimal for the operations it supports.

Steps in the Solution:

  • Initial Construction: Convert the initial array into a form suitable for the Fenwick Tree, marking each position based on whether the number has a single set bit.
  • Update Function: Modify the tree when an update occurs, ensuring the counts reflect the change.
  • Query Function: Efficiently compute the number of elements with a single set bit in any given range.
C++
Java Python JavaScript

Output
[1, 2]

Time Complexity: O((N+Q)*logN) for the initial construction and O(logN) for each update and query, leading to O((N+Q)logN) overall.

Auxilary Space: O(N) for storing the Fenwick Tree.




Next Article

Similar Reads