Skip to content
Related Articles
Get the best out of our app
GFG App
Open App
geeksforgeeks
Browser
Continue

Related Articles

Implementation of Dynamic Segment Trees with Poly Hash Tables

Improve Article
Save Article
Like Article
Improve Article
Save Article
Like Article

Dynamic Segment Trees with Poly Hash Tables is a data structure that combines the benefits of both dynamic segment trees and hash tables to efficiently handle range queries on an array of elements.

To understand this topic, let’s start with an example. Consider an array of N elements {1, 2, 3, …, N}. We want to support two operations on this array:

  • Query(l, r): Return the sum of all the elements in the array between indices l and r.
  • Update(i, x): Replace the value of the element at index i with x.

We can use a segment tree to support these operations efficiently in O(log N) time. However, the segment tree requires us to know the size of the array in advance and does not allow us to insert or delete elements from the array. This is where dynamic segment trees come in. A dynamic segment tree is a data structure that can handle insertions and deletions in addition to the query and update operations.

Now let’s consider the problem of adding hash tables to dynamic segment trees. A hash table is a data structure that allows us to store and retrieve values using keys in constant time. We can use hash tables to efficiently handle range queries that involve non-numeric values, such as strings or objects.

Poly Hash Tables:

The poly hash table is a type of hash table that uses a polynomial hash function to map keys to indices in the table. The polynomial hash function takes the form of 
h(k) = (a_0 + a_1k + a_2k^2 + … + a_n*k^n) % p, where k is the key, a_i are coefficients, and p is a large prime number. The polynomial hash function has the property that it produces a unique index for each key with high probability, making it a good choice for hash tables.

To implement a dynamic segment tree with poly hash tables, we can use the following steps:

  • Divide the array into segments of fixed length. For example, we can divide the array into segments of length sqrt(N).
  • Create a dynamic segment tree that stores the sum of elements in each segment.
  • For each segment, create a poly hash table that maps elements in the segment to their indices in the segment.
  • When we insert or delete an element in the array, update the corresponding segment in the dynamic segment tree and the corresponding key-value pair in the poly hash table.

To handle range queries, split the query range into segments and compute the sum of segments using the dynamic segment tree. For each segment, look up the corresponding keys in the poly hash table and add up their values to get the final result.

Let’s see an example implementation of this algorithm in C++:

C++




// C++ code for the above approach
#include <bits/stdc++.h>
using namespace std;
  
// This class is used to compute the hash
// of a polynomial with coefficients given
// by the vector a. The hash is
// computed modulo p.
class PolyHash {
public:
    PolyHash(const vector<int>& a, int p)
        : a(a), p(p)
    {
    }
  
    // Computes the hash of the
    // polynomial at the point k.
    int hash(int k) const
    {
        int result = 0;
        for (int i = 0; i < a.size(); i++) {
            result = (result + a[i] * k) % p;
        }
        return result;
    }
  
private:
    vector<int> a;
  
    // Coefficients of the polynomial.
    int p;
  
    // Modulus used to compute the hash.
};
  
// This class implements a dynamic
// segment tree with polyhash.
class DynamicSegmentTreeWithPolyHash {
public:
    DynamicSegmentTreeWithPolyHash(const vector<int>& a)
        : a(a)
    {
  
        // The segment size is chosen to
        // be sqrt(n), where n
        // is the size of a.
        segmentSize = sqrt(a.size());
  
        // We allocate enough memory for
        // 2*n nodes in the segment tree.
        segmentTree.resize(2 * a.size());
  
        // We create a vector of
        // unordered_maps, one for
        // each segment of size sqrt(n).
        for (int i = 0; i < a.size(); i += segmentSize) {
            unordered_map<int, int> polyHashTable;
            // We compute the hash of the
            // subarray [i, i+segmentSize]
            // and store it in
            // polyHashTable for each
            // element in that subarray.
            for (int j = i;
                 j < min(i + segmentSize, (int)a.size());
                 j++) {
                polyHashTable[a[j]] = j - i;
                segmentTree[a.size() + i / segmentSize]
                    += a[j];
            }
  
            // We store the hash table for
            // this segment in polyHashTables.
            polyHashTables.push_back(polyHashTable);
        }
    }
  
    // Returns the sum of elements in
    // the range [l, r] of the array a.
    int query(int l, int r) const
    {
        int sum = 0;
  
        // We loop over the elements in
        // the range [l, r] and add
        // them to sum.
        for (int i = l; i <= r;) {
  
            // If we are at the beginning
            // of a segment, and the segment
            // contains the entire range
            // [i, r], we can add the
            // precomputed sum of the
            // segment to sum and skip
            // to the next segment.
            if (i % segmentSize == 0
                && i + segmentSize - 1 < r) {
                sum += segmentTree[a.size()
                                   + i / segmentSize];
                i += segmentSize;
            }
  
            // Otherwise, we simply add
            // the current element to sum
            // and move to the next element.
            else {
                sum += a[i];
                i++;
            }
        }
        return sum;
    }
  
    // Updates the value of a[i] to x.
    void update(int i, int x)
    {
  
        // We first find the segment
        // that contains a[i].
        int segmentIndex = i / segmentSize;
        int segmentStart = segmentIndex * segmentSize;
        int segmentEnd = min(segmentStart + segmentSize,
                             (int)a.size());
  
        // We update the value of a[i] in
        // the array and update the sum of
        // the segment in the segment tree.
        segmentTree[a.size() + +segmentIndex] += x - a[i];
  
        // Update the corresponding node
        // of the segment tree with the
        // difference between the new and
        // old values of the element.
        polyHashTables[segmentIndex].erase(a[i]);
        polyHashTables[segmentIndex][x] = i - segmentStart;
        a[i] = x;
    }
  
    void insert(int i, int x)
  
    // In the insert method, a new
    // element x is inserted at index i
    // of the vector a, and then the
    // rebuild method is called to
    // rebuild the segment tree
    // using the updated vector.
    {
        a.insert(a.begin() + i, x);
        rebuild();
    }
  
    void remove(int i)
  
    // In the remove method, the element
    // at index i of the vector a is
    // removed, and then the rebuild method
    // is called to rebuild the segment
    // tree using the updated vector.
    {
        a.erase(a.begin() + i);
        rebuild();
    }
  
private:
    void rebuild()
    {
        int n = a.size();
  
        // The rebuild method is called in
        // both insert and remove methods
        // to rebuild the segment tree
        // using the updated vector.
        segmentSize = sqrt(n);
        segmentTree.clear();
  
        // It first calculates the new
        // segmentSize using the
        // updated size of the vector.
        segmentTree.resize(2 * n);
        polyHashTables.clear();
  
        // Then it clears the segmentTree
        // and polyHashTables vectors and
        // rebuilds them by iterating
        // through the new vector in
        // segments of size segmentSize.
        for (int i = 0; i < n; i += segmentSize) {
            unordered_map<int, int> polyHashTable;
            for (int j = i; j < min(i + segmentSize, n);
                 j++) {
                polyHashTable[a[j]] = j - i;
  
                // In each segment, it
                // creates a new polyHashTable
                // and adds the values to it,
                // and calculates the sum of
                // the segment and adds
                // it to the segmentTree.
                segmentTree[n + i / segmentSize] += a[j];
            }
            polyHashTables.push_back(polyHashTable);
  
            // Finally, the polyHashTable is
            // added to the polyHashTables
            // vector.
        }
    }
  
private:
    vector<int> a;
    int segmentSize;
    vector<int> segmentTree;
    vector<unordered_map<int, int> > polyHashTables;
};
  
// Driver's code
int main()
{
    vector<int> a = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  
    // Initializes a vector a with 10
    // integers from 1 to 10.
    DynamicSegmentTreeWithPolyHash dst(a);
  
    // It creates an instance of the
    // DynamicSegmentTreeWithPolyHash
    // class with the vector a.
    cout << "Query(0, 4) = " << dst.query(0, 4) << endl;
  
    // It calls the query method of the
    // dst object with arguments (0, 4)
    // and prints the result. expected
    // output: 15
    dst.update(3, 100);
  
    // It calls the update method of the
    // dst object with  arguments (3, 100).
    cout << "Query(2, 5) = " << dst.query(2, 5) << endl;
  
    // It calls the query method of the
    // dst object with arguments (2, 5)
    // and prints the result. This should
    // output "Query(2, 5) = 114".
    // expected output: 114
    return 0;
}


Output

Query(0, 4) = 15
Query(2, 5) = 114

Time Complexity: O(N*logN)
Auxiliary Space: O(N)

Conclusion:

In conclusion, the use of Dynamic Segment Trees with Poly Hash Tables is a powerful data structure that provides an efficient and flexible way to maintain dynamic intervals in an array. It is particularly useful in scenarios where the range of values is not known beforehand or changes frequently, making it an important tool in various applications such as online algorithms and data compression.


My Personal Notes arrow_drop_up
Last Updated : 14 Mar, 2023
Like Article
Save Article
Similar Reads
Related Tutorials