 GFG App
Open App Browser
Continue

# Implementation of Dynamic Segment Trees with Poly Hash Tables

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 ` `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 > 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