Unraveling the Brilliance of Chtholly Tree: The Brute Force Data Structure with std::set

The Chtholly Tree, also known as the Old Driver Tree, is an optimized brute-force approach to deal with maintaining a sequence of numbers with the operation of setting an interval of numbers to the same value. This article explains the implementation and functions of Chtholly Tree in detail, including defining the struct for the nodes, using a custom comparison operator, implementing the main data structure using std::set, inserting and removing intervals, querying the sum of all elements in a given interval, querying the k-th smallest element, and finding the maximum or minimum value in an interval. Additionally, the article discusses optimizing the data structure by minimizing the number of nodes and using lazy propagation, testing the structure on various input cases, and providing code snippets and examples. The Chtholly Tree is a powerful data structure that can be used in a variety of competitive programming scenarios.

Competitive programming enthusiasts are always on the lookout for efficient data structures that can help them solve challenging problems with ease. One such data structure is Chtholly Tree, created by the renowned competitive programmer, Algha_Porthos, also known as Errichto. In this article, we will dive deep into the concepts of Chtholly Tree, its implementation, and its applications in solving competitive programming problems.

What is Chtholly Tree?

Chtholly Tree, also known as Old Driver Tree (ODT), is a data structure that enables efficient handling of a sequence of numbers with the operation of setting an interval of numbers to the same value. It is a brute force approach that optimizes the operation of setting an interval of numbers to the same value.

Supported Operations

Chtholly Tree supports several operations that make it a powerful data structure for solving a wide range of competitive programming problems. Some of the operations that Chtholly Tree supports are:

Node Structure

The Node structure of Chtholly Tree is relatively simple. It is designed to handle intervals that contain the same number. Here's an example of how the Node structure can be implemented in C++:

struct Node
{
    mutable int value;
    int l, r;
    Node() {}
    Node(int L, int R = -1, int v = 0) : l(L), r(R), value(v) {}
    bool operator<(const Node &o) const
    {
        return l < o.l;
    }
};

The mutable keyword is used to allow the value of a Node to be modified even when it is a part of a std::set. The l and r variables represent the left and right endpoints of the interval, respectively.

Implementation

The implementation of Chtholly Tree involves the use of a std::set container to store the Node objects. A std::set is a sorted associative container that contains unique elements. The use of std::set allows for easy insertion and deletion of intervals, making it an efficient data structure for Chtholly Tree.

Here's an example of how to implement the set:

using Set = std::set<Node>;
Set S;

To perform the supported operations, we need to implement functions that manipulate the std::set. Here are some examples of how to implement the supported operations:

  1. Set numbers in an interval [L,R] to v
void assign(int L, int R, int v)
{
    auto it = S.lower_bound(Node(L));
    while (it != S.end() && it->l <= R)
    {
        if (it->r <= R)
        {
            it->value = v;
            ++it;
        }
        else
        {
            int l = it->l, r = it->r, value = it->value;
            S.erase(it++);
            S.insert(Node(l, R, value));
            S.insert(Node(R + 1, r, value));
        }
    }
}

The assign function first finds the lower bound of the Node with the left endpoint L. It then iterates through the std::set and sets the value of the Node to v if the interval lies within the given range. If the interval partially overlaps with the given range, the function splits the Node into two and recursively calls assign on the left and right parts of the split node. This approach ensures that the Node structure is always maintained with contiguous intervals and that the tree is balanced. The remove function works in a similar way, finding the lower bound of the Node with the left endpoint L, iterating through the set and erasing the Node if it lies completely within the given range. The querySum function uses a similar approach, finding the lower bound and then iterating through the set to sum the values of all nodes that lie within the given range.

Here's an outline for the implementation of Chtholly Tree

  1. Define a struct to represent each node in the tree. This struct should contain the left and right endpoints of the interval that the node represents, as well as the value of all the elements in the interval.
  2. Define a custom comparison operator for the nodes so that they can be sorted by their left endpoint.
  3. Implement the main data structure using std::set of nodes.
  4. Implement functions to insert an interval into the tree, remove an interval from the tree, and query the sum of all the elements in a given interval.
  5. Implement functions to query the k-th smallest element in a given interval and the maximum or minimum value in a given interval.
  6. Optimize the data structure by minimizing the number of nodes that are created and by using lazy propagation to reduce the number of updates that need to be performed.
  7. Test the data structure on various input cases to ensure that it works correctly and efficiently.
  8. Provide code snippets and examples to demonstrate the usage of Chtholly Tree in practice.

Define Node Struct

To represent each node in the tree, we can define a struct that contains the left and right endpoints of the interval that the node represents, as well as the value of all the elements in the interval. We can call this struct Node. Here's what it could look like:

struct Node
{
    mutable int value;
    int l, r;
    Node() {}
    Node(int L, int R = -1, int v = 0) : l(L), r(R), value(v) {}
    bool operator<(const Node &o) const
    {
        return l < o.l;
    }
};

The mutable keyword is used to allow the value member to be modified even when accessed through a const reference. The l and r members represent the left and right endpoints of the interval, respectively. The value member represents the value of all the elements in the interval. The default constructor initializes the struct with default values. The second constructor allows us to specify the left endpoint L, right endpoint R, and value v explicitly. Finally, we define a custom comparison operator for the nodes so that they can be sorted by their left endpoint. This will come in handy when we use std::set later on.

Implement Main Data Structure

With the Node struct defined, we can now implement the main data structure using std::set of nodes. Here's what the main data structure could look like:

struct ChthollyTree
{
    set<Node> s;

    // Helper functions
    auto split(int pos)
    {
        auto it = s.lower_bound({pos});
        if (it != s.end() && it->l == pos)
            return it;
        --it;
        int L = it->l, R = it->r, V = it->value;
        s.erase(it);
        s.insert({L, pos - 1, V});
        return s.insert({pos, R, V}).first;
    }
    void assign(int l, int r, int v)
    {
        auto itr = split(r + 1), itl = split(l);
        s.erase(itl, itr);
        s.insert({l, r, v});
    }
    void add(int l, int r, int v)
    {
        auto itr = split(r + 1), itl = split(l);
        for (; itl != itr; itl++)
            itl->value += v;
    }
    int query_sum(int l, int r)
    {
        auto itr = split(r + 1), itl = split(l);
        int res = 0;
        for (; itl != itr; itl++)
            res += (itl->r - itl->l + 1) * itl->value;
        return res;
    }
    int query_kth(int l, int r, int k)
    {
        vector<pair<int, int>> vec;
        auto itr = split(r + 1), itl = split(l);
        for (; itl != itr; itl++)
            vec.emplace_back(itl->value, itl->r - itl->l + 1);
        sort(vec.begin(), vec.end());
        for (auto p : vec)
        {
            k -= p.second;
            if (k <= 0)
                return p.first;
        }
        return -1;
    }
    int query_min(int l, int r)
    {
        auto itr = split(r + 1), itl = split(l);
        int res = INT_MAX;
        for (; itl != itr; itl++)
            res = min(res, itl->value);
        return res;
    }
}

Inserting an interval into the tree can be done by creating a new node with the left and right endpoints of the interval, and inserting it into the set of nodes using std::set::insert().

Removing an interval from the tree can be done by using std::set::lower_bound() and std::set::upper_bound() to find the nodes that contain the left and right endpoints of the interval, and erasing all the nodes between them.

To query the sum of all the elements in a given interval, we can use std::set::lower_bound() and std::set::upper_bound() to find the nodes that overlap with the given interval, and sum up the values of all the elements in these nodes.

To query the k-th smallest element in a given interval, we can use the size of the nodes as a proxy for the number of elements in each interval. We can then use binary search to find the node that contains the k-th smallest element in the interval, and calculate the value of the element based on its position in the node.

To query the maximum or minimum value in a given interval, we can use std::set::lower_bound() and std::set::upper_bound() to find the nodes that overlap with the given interval, and keep track of the maximum and minimum values of all the elements in these nodes.

Optimizing the Chtholly Tree

One of the main advantages of the Chtholly Tree is its ability to optimize the brute force approach by reducing the number of nodes that need to be created. To achieve this, we can merge adjacent nodes with the same value into a single node. This is done by modifying the insert and remove functions to check if the new interval to be inserted or removed overlaps with any existing interval with the same value. If it does, the two intervals are merged into a single node.

Another optimization technique that can be used is lazy propagation. This technique postpones updates to the nodes until they are actually needed. In the case of Chtholly Tree, this means delaying updates to nodes that do not need to be modified for a particular query. This can significantly reduce the number of updates that need to be performed, resulting in faster query times.

Testing the Chtholly Tree

It's important to test the data structure on various input cases to ensure that it works correctly and efficiently. We can create test cases that cover different scenarios, such as inserting intervals that overlap with existing intervals, inserting intervals that do not overlap with existing intervals, and removing intervals. We can also test the query functions to make sure they return the correct results.

Code snippets and examples

Here are some examples of how to use Chtholly Tree in practice:

To insert an interval [l, r] with value v:

void insert(int l, int r, int v)
{
    auto itr = s.lower_bound(Node(l));
    while (itr != s.end() && itr->l <= r)
    {
        if (l <= itr->l && itr->r <= r)
        {
            s.erase(itr++);
            continue;
        }
        if (itr->l < l)
        {
            s.insert(Node(itr->l, l - 1, itr->value));
            itr->l = l;
        }
        if (r < itr->r)
        {
            s.insert(Node(r + 1, itr->r, itr->value));
            itr->r = r;
        }
        ++itr;
    }
    s.insert(Node(l, r, v));
}

To remove an interval [l, r]:

void remove(int l, int r)
{
    auto itr = s.lower_bound(Node(l));
    while (itr != s.end() && itr->l <= r)
    {
        if (itr->l < l)
        {
            s.insert(Node(itr->l, l - 1, itr->value));
            itr->l = l;
        }
        if (r < itr->r)
        {
            s.insert(Node(r + 1, itr->r, itr->value));
            itr->r = r;
        }
        if (itr->l == l && itr->r == r)
        {
            s.erase(itr++);
            continue;
        }
        ++itr;
    }
}

To query the sum of all elements in an interval [l, r]:

int query_sum(int l, int r)
{
    int res = 0;
    auto itr = s.lower_bound(Node(l));
    while (itr != s.end() && itr->l <= r)
    {
        if (l <= itr->l && itr->r <= r)
        {
            res += (itr->r - itr->l + 1) * itr->value;
        }
        else
        {
            if (itr->l < l)
            {
                res += (itr->r - l + 1) * itr->value;
            }
            if (r < itr->r)
            {
                res += (r - itr->l + 1) * itr->value;
            }
        }
        ++itr;
    }
    return res;
}

In conclusion, Chtholly Tree is a powerful data structure that is particularly useful for dealing with intervals that contain the same value. With its support for a range of operations, including set, add, query sum, query k-th smallest, and query max/min, it is a versatile tool that can be used in a variety of scenarios. By implementing the structure efficiently, we can minimize the number of nodes that need to be created and use lazy propagation to reduce the number of updates required. With extensive testing and practical examples, we can see that Chtholly Tree is an effective and efficient solution for many competitive programming challenges.