Open In App

Implementation of Dynamic Segment Trees with Poly Hash Tables

Improve
Improve
Like Article
Like
Save
Share
Report

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;
}
Java
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Main {
    // This class is used to compute the hash of a polynomial with coefficients given by the vector a.
    static class PolyHash {
        private final List<Integer> a;
        private final int p;
        public PolyHash(List<Integer> a, int p) {
            this.a = a;
            this.p = p;
        }
        // Computes the hash of the polynomial at point k.
        public int hash(int k) {
            int result = 0;
            for (int i = 0; i < a.size(); i++) {
                result = (result + a.get(i) * k) % p;
            }
            return result;
        }
    }
    // This class implements a dynamic segment tree with the polyhash.
    static class GFG {
        private final List<Integer> a;
        private final int segmentSize;
        private final List<Integer> segmentTree;
        private final List<Map<Integer, Integer>> polyHashTables;

        public GFG(List<Integer> a) {
            this.a = a;
            this.segmentSize = (int) Math.sqrt(a.size());
            this.segmentTree = new ArrayList<>();
            this.polyHashTables = new ArrayList<>();
            for (int i = 0; i < a.size(); i += segmentSize) {
                Map<Integer, Integer> polyHashTable = new HashMap<>();
                int sum = 0;
                for (int j = i; j < Math.min(i + segmentSize, a.size()); j++) {
                    polyHashTable.put(a.get(j), j - i);
                    sum += a.get(j);
                }
                segmentTree.add(sum);
                polyHashTables.add(polyHashTable);
            }
        }
        // Returns the sum of elements in the range [l, r] of array a.
        public int query(int l, int r) {
            int sum = 0;
            for (int i = l; i <= r; i++) {
                if (i % segmentSize == 0 && i + segmentSize - 1 <= r) {
                    sum += segmentTree.get(i / segmentSize);
                    i += segmentSize - 1;
                } else {
                    sum += a.get(i);
                }
            }
            return sum;
        }
        // Updates the value of a[i] to x.
        public void update(int i, int x) {
            int segmentIndex = i / segmentSize;
            int segmentStart = segmentIndex * segmentSize;
            int segmentEnd = Math.min(segmentStart + segmentSize, a.size());
            segmentTree.set(segmentIndex, segmentTree.get(segmentIndex) + x - a.get(i));
            polyHashTables.get(segmentIndex).remove(a.get(i));
            polyHashTables.get(segmentIndex).put(x, i - segmentStart);
            a.set(i, x);
        }
        // Inserts a new element x at index i.
        public void insert(int i, int x) {
            a.add(i, x);
            rebuild();
        }
        // Removes the element at index i.
        public void remove(int i) {
            a.remove(i);
            rebuild();
        }
        // Rebuilds the segment tree and polyhash tables.
        private void rebuild() {
            segmentTree.clear();
            polyHashTables.clear();
            for (int i = 0; i < a.size(); i += segmentSize) {
                Map<Integer, Integer> polyHashTable = new HashMap<>();
                int sum = 0;
                for (int j = i; j < Math.min(i + segmentSize, a.size()); j++) {
                    polyHashTable.put(a.get(j), j - i);
                    sum += a.get(j);
                }
                segmentTree.add(sum);
                polyHashTables.add(polyHashTable);
            }
        }
    }
    // Main method
    public static void main(String[] args) {
        List<Integer> a = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            a.add(i);
        }
        GFG dst = new GFG(a);
        System.out.println("Query(0, 4) = " + dst.query(0, 4));
        dst.update(3, 100);
        System.out.println("Query(2, 5) = " + dst.query(2, 5));
    }
}
C#
using System;
using System.Collections.Generic;

public class PolyHash
{
    private readonly List<int> a;
    private readonly int p; // Modulus used to compute the hash

    // Constructor to initialize the polynomial coefficients and modulus
    public PolyHash(List<int> a, int p)
    {
        this.a = a;
        this.p = p;
    }

    // Method to compute the hash of the polynomial at a given point k
    public int Hash(int k)
    {
        int result = 0;
        for (int i = 0; i < a.Count; i++)
        {
            // Polynomial hash calculation
            result = (result + a[i] * k) % p;
        }
        return result;
    }
}

public class DynamicSegmentTreeWithPolyHash
{
    private readonly List<int> a; // Input array
    private int segmentSize; // Size of each segment
    private List<int> segmentTree; // Segment tree to store sums
    private List<Dictionary<int, int>> polyHashTables; // Hash tables for each segment

    // Constructor to initialize the dynamic segment tree with polynomial hash
    public DynamicSegmentTreeWithPolyHash(List<int> a)
    {
        this.a = a;
        segmentSize = (int)Math.Sqrt(a.Count);
        segmentTree = new List<int>(2 * a.Count); // Initialize segment tree
        polyHashTables = new List<Dictionary<int, int>>(); // Initialize hash tables

        for (int i = 0; i < 2 * a.Count; i++)
        {
            segmentTree.Add(0); // Initialize segment tree nodes
        }

        // Divide the input array into segments and compute hash tables for each segment
        for (int i = 0; i < a.Count; i += segmentSize)
        {
            var polyHashTable = new Dictionary<int, int>(); // Create hash table for current segment
            for (int j = i; j < Math.Min(i + segmentSize, a.Count); j++)
            {
                polyHashTable[a[j]] = j - i; // Store element index relative to segment start
                segmentTree[a.Count + i / segmentSize] += a[j]; // Update segment tree with element sum
            }
            polyHashTables.Add(polyHashTable); // Store hash table for current segment
        }
    }

    // Method to query the sum of elements in a range [l, r] of the input array
    public int Query(int l, int r)
    {
        int sum = 0;

        // Loop through the range [l, r] and accumulate the sum
        for (int i = l; i <= r;)
        {
            if (i % segmentSize == 0 && i + segmentSize - 1 < r)
            {
                sum += segmentTree[a.Count + i / segmentSize]; // Add precomputed segment sum
                i += segmentSize; // Move to next segment
            }
            else
            {
                sum += a[i]; // Add individual element
                i++; // Move to next element
            }
        }
        return sum; // Return the sum
    }

    // Method to update the value of an element at index i in the input array
    public void Update(int i, int x)
    {
        int segmentIndex = i / segmentSize; // Get the segment index containing the element
        int segmentStart = segmentIndex * segmentSize; // Get the start index of the segment
        // int segmentEnd = Math.Min(segmentStart + segmentSize, a.Count); // Get the end index of the segment

        segmentTree[a.Count + segmentIndex] += x - a[i]; // Update segment tree with new element value

        // Update the hash table of the segment with the new element value
        polyHashTables[segmentIndex].Remove(a[i]); // Remove old element from hash table
        polyHashTables[segmentIndex][x] = i - segmentStart; // Add new element to hash table
        a[i] = x; // Update element value in input array
    }

    // Method to insert a new element x at index i in the input array
    public void Insert(int i, int x)
    {
        a.Insert(i, x); // Insert new element into input array
        Rebuild(); // Rebuild the segment tree and hash tables
    }

    // Method to remove an element at index i from the input array
    public void Remove(int i)
    {
        a.RemoveAt(i); // Remove element from input array
        Rebuild(); // Rebuild the segment tree and hash tables
    }

    // Method to rebuild the segment tree and hash tables based on the current input array
    private void Rebuild()
    {
        int n = a.Count; // Get the size of the input array
        segmentSize = (int)Math.Sqrt(n); // Calculate the new segment size
        segmentTree = new List<int>(2 * n); // Initialize segment tree
        polyHashTables.Clear(); // Clear existing hash tables

        for (int i = 0; i < 2 * n; i++)
        {
            segmentTree.Add(0); // Initialize segment tree nodes
        }

        // Divide the input array into segments and compute hash tables for each segment
        for (int i = 0; i < n; i += segmentSize)
        {
            var polyHashTable = new Dictionary<int, int>(); // Create hash table for current segment
            for (int j = i; j < Math.Min(i + segmentSize, n); j++)
            {
                polyHashTable[a[j]] = j - i; // Store element index relative to segment start
                segmentTree[n + i / segmentSize] += a[j]; // Update segment tree with element sum
            }
            polyHashTables.Add(polyHashTable); // Store hash table for current segment
        }
    }
}

// Main class to test the DynamicSegmentTreeWithPolyHash implementation
public class MainClass
{
    public static void Main(string[] args)
    {
        List<int> a = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; // Input array

        // Initialize a dynamic segment tree with polynomial hash using the input array
        DynamicSegmentTreeWithPolyHash dst = new DynamicSegmentTreeWithPolyHash(a);

        // Query the sum of elements in the range [0, 4] and print the result
        Console.WriteLine("Query(0, 4) = " + dst.Query(0, 4));

        // Update the value of element at index 3 to 100
        dst.Update(3, 100);

        // Query the sum of elements in the range [2, 5] after update and print the result
        Console.WriteLine("Query(2, 5) = " + dst.Query(2, 5));
    }
}
JavaScript
// Class to compute the hash of a polynomial with coefficients given by the vector a
class PolyHash {
    constructor(a, p) {
        this.a = a;
        this.p = p;
    }

    // Computes the hash of the polynomial at point k
    hash(k) {
        let result = 0;
        for (let i = 0; i < this.a.length; i++) {
            result = (result + this.a[i] * k) % this.p;
        }
        return result;
    }
}

// Class implementing a dynamic segment tree with polyhash
class GFG {
    constructor(a) {
        this.a = a;
        this.segmentSize = Math.sqrt(a.length);
        this.segmentTree = [];
        this.polyHashTables = [];
        for (let i = 0; i < a.length; i += this.segmentSize) {
            const polyHashTable = new Map();
            let sum = 0;
            for (let j = i; j < Math.min(i + this.segmentSize, a.length); j++) {
                polyHashTable.set(a[j], j - i);
                sum += a[j];
            }
            this.segmentTree.push(sum);
            this.polyHashTables.push(polyHashTable);
        }
    }

    // Returns the sum of elements in the range [l, r] of array a
    query(l, r) {
        let sum = 0;
        for (let i = l; i <= r; i++) {
            if (i % this.segmentSize === 0 && i + this.segmentSize - 1 <= r) {
                sum += this.segmentTree[i / this.segmentSize];
                i += this.segmentSize - 1;
            } else {
                sum += this.a[i];
            }
        }
        return sum;
    }

    // Updates the value of a[i] to x
    update(i, x) {
        const segmentIndex = Math.floor(i / this.segmentSize);
        const segmentStart = segmentIndex * this.segmentSize;
        const segmentEnd = Math.min(segmentStart + this.segmentSize, this.a.length);
        this.segmentTree[segmentIndex] += x - this.a[i];
        this.polyHashTables[segmentIndex].delete(this.a[i]);
        this.polyHashTables[segmentIndex].set(x, i - segmentStart);
        this.a[i] = x;
    }

    // Inserts a new element x at index i
    insert(i, x) {
        this.a.splice(i, 0, x);
        this.rebuild();
    }

    // Removes the element at index i
    remove(i) {
        this.a.splice(i, 1);
        this.rebuild();
    }

    // Rebuilds the segment tree and polyhash tables
    rebuild() {
        this.segmentTree = [];
        this.polyHashTables = [];
        for (let i = 0; i < this.a.length; i += this.segmentSize) {
            const polyHashTable = new Map();
            let sum = 0;
            for (let j = i; j < Math.min(i + this.segmentSize, this.a.length); j++) {
                polyHashTable.set(this.a[j], j - i);
                sum += this.a[j];
            }
            this.segmentTree.push(sum);
            this.polyHashTables.push(polyHashTable);
        }
    }
}

// Main method
function main() {
    const a = [];
    for (let i = 1; i <= 10; i++) {
        a.push(i);
    }
    const dst = new GFG(a);
    console.log("Query(0, 4) =", dst.query(0, 4));
    dst.update(3, 100);
    console.log("Query(2, 5) =", dst.query(2, 5));
}

// Call the main function to execute the program
main();

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.



Last Updated : 28 Mar, 2024
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads