Written by Johannes Kapfhammer.
Contents
A treap (conjunction of the words tree and heap) is a binary search tree that uses randomization and the binary heap property to maintain balance with a high probability. It maintains a dynamic set of ordered keys and allows binary searches among these keys.
Treaps can be used for two use cases: a regular treap as a balanced binary search tree, or an implicit cartesian tree as rope data structure. This article covers both.
Balanced Binary Search Tree
Some of this text has been taken from this SOIminar article by Nicolas Camenisch.
Each node of a treap contains two values: A key (ordered using binary search tree properties) and a priority (ordered using heap properties). The heap order can either be maintained using a max heap or a min heap. In all following examples a max heap will be used.
The structure of a treap follows these rules:
- The nodes in the left subtree of the root will have key values that are less than the key of the root.
- The nodes in the right subtree of the root will have key values that are greater than the key of the root.
- All priorities are unique.
- The priority value of the root is greater than the priority values of its children.
These rules ensure that there is only one unique and valid structure for each possible set of keys and priorities.
Basic Treap
Below is a basic implementation of a heap, supporting only the operations insert, erase and contains.
Note that a std::multiset<T> or std::unordered_multiset<T> can serve the same functionality.
Accessing Elements by Order Index
To return the element at a given index, we need to keep track of the size of each subtree.
Note that policy based data structures can used for this purpose as well.
You can solve the task ORDERSET with this.
Range Queries
Similar to Advanced Segment Trees, you can have arbitrary combiner functions.
Note that all functions we had so far (merge, split, insert, erase, contains, find_by_order, order_of_key) are identical. The only thing that needed to change was the representation of our node struct and the function recompute.
Below is a code that solves the problem TREAP.
It’s possible to extend this idea to lazy updates. We will see more of that below.
Rope
This text is taken from https://cp-algorithms.com/data_structures/treap.html.
Implicit treap is a simple modification of the regular treap which is a very powerful data structure. In fact, implicit treap can be considered as an array with the following procedures implemented (all in in the online mode):
- Inserting an element in the array in any location
- Removal of an arbitrary element
- Finding sum, minimum / maximum element etc. on an arbitrary interval
- Addition, painting on an arbitrary interval
- Reversing elements on an arbitrary interval
The idea is that the keys should be indices of the elements in the array. But we will not store these values explicitly (otherwise, for example, inserting an element would cause changes of the key in nodes of the tree).
Note that the key of a node is the number of nodes less than it (such nodes can be present not only in its left subtree but also in left subtrees of its ancestors). More specifically, the implicit key for some node T is the number of vertices in the left subtree of this node plus similar values for each ancestor P of the node T, if T is in the right subtree of P.
Now it’s clear how to calculate the implicit key of current node quickly. Since in all operations we arrive to any node by descending in the tree, we can just accumulate this sum and pass it to the function. If we go to the left subtree, the accumulated sum does not change, if we go to the right subtree it increases by .
Basic Implicit Treap
Below is an implementation for the task AROPE.
Range Queries
As with regular treaps, it’s easy to do range queries. The code for the query is exactly the same as before, so the code is not shown again. Below is code shown without Metadata struct, just inplace.
Example problem: QMAX3VN.
Lazy Updates
Here, again, one can do arbitrary operations. Below is the implementation of reversing a range, which can be used to solve the task AROPE3.
The idea is exactly the same as for a lazy segment tree. We implement apply, combine and neutral_element for the specific task, have helper functions apply and propagate. The only change in the query functions was to insert propagate when appropriate.
When not to use treaps
Treaps (or any balanced binary search tree for that matter) are very powerful data structures. However, it’s not always wise to implement them during a contest. Before coding them, please double-check that you really need them.
- Can you modify the queries such that a std::set<T> is sufficient?
- Can it be solved with policy based data structures?
- Can you do coordinate-compression such that a segment tree is sufficient?
- Is a sparse segment tree enough?
- Can I get points in other subtasks? (Then rather go for them.)
Sparse segment trees are segment trees where instead of storing the nodes in a heap, each node has a left and a right pointer (like in a treap). The advantage of sparse segment trees over treaps is that you don’t need any kind of rebalancing. In theory, segment trees are limited to storing integers, however for most tasks this is not a big issue.