Chtholly Tree / Color Segment Amortization
Introduction
Chtholly Tree, also known as Old Driver Tree (ODT). It originated from CF896C.
This name refers to a technique of "using balanced trees (std::set, std::map, etc.) or linked lists (std::list, handwritten linked lists, etc.) to maintain color segments with amortized complexity", rather than a specific data structure. Its core idea is to merge consecutive intervals with the same value into a single node for processing. Compared to traditional data structures like segment trees, for problems involving interval assignment operations, Chtholly Tree can more conveniently maintain the value of each assigned interval.
Implementation (std::set)
Node Type
1 2 3 4 5 6 7 8 | |
Here, int v is the additional data you specify.
What does the mutable keyword mean?
mutable means "mutable", allowing us to modify the value of v in subsequent operations. In C++, mutable is used to bypass const restrictions. A variable modified by mutable (mutable can only modify non-static data members of a class) will always be mutable, even within a const function.
This means we can directly modify the v value of an element already inserted into the set, without needing to remove and reinsert the element.
Node Storage
We want to maintain all nodes such that the left endpoints of the intervals they represent are monotonically increasing and pairwise disjoint, and ideally, the union of all intervals is a large continuous range. Here, using std::set as an example, we maintain all nodes with a set<Node_t> odt;.
During initialization, insert an extremely long interval into the Chtholly Tree (e.g., if the problem requires maintaining information from position \(1\) to \(n\), insert the interval \([1,n+1]\)).
split Operation
The split operation is the core of Chtholly Tree. It takes a position \(x\), splits the original interval containing point \(x\) (denoted as \([l, r]\)) into two intervals \([l, x)\) and \([x, r]\), and returns an iterator pointing to the latter.
Reference code:
1 2 3 4 5 6 7 8 9 | |
On compilers that do not support using auto for return type deduction, you can change the function's return type to set<Node_t>::iterator.
assign Operation
Another important operation: assign. Used to assign a value to an interval. Suppose we want to assign value \(v\) to interval \([l,r]\).
First, extract the interval \([l, r]\). Call split(r + 1), split(l) in sequence, and denote the returned iterators as \(itr, itl\). Then the iterator range \([itl, itr)\) points to all intervals in the Chtholly Tree contained within \([l,r]\).
Then, delete the original information. std::set has a member method erase with a signature like iterator erase( const_iterator first, const_iterator last );, which can remove elements in the range [first; last). So we call odt.erase(itl, itr); to delete the original information.
Finally, insert the new value for interval \([l,r]\). Call odt.insert(Node_t(l, r, v)).
Reference code:
1 2 3 4 5 | |
Why do we need to split(r + 1) before split(l)?
- The
std::set::erasemethod invalidates references and iterators to the erased elements. Other references and iterators are unaffected. - The
std::set::insertmethod does not invalidate any iterators or references. - The
splitoperation splits an interval. After callingsplit(r + 1), \(r + 1\) becomes the left endpoint of the right new interval. Then splitting the left interval will definitely not access the interval with \(r + 1\) as its left endpoint, thus not splitting it, and deleting the interval with \(r + 1\) as its left endpoint will not invalidate iterators. Conversely, doingsplit(l)first, thensplit(r + 1), might delete the interval with \(l\) as its left endpoint, invalidating iterators.
perform Operation
Extract an interval from the Chtholly Tree and perform operations on it. Similar to the assign operation, but instead of deleting the interval, we traverse it.
Reference code:
1 2 3 4 5 6 | |
Note that such extraction operations should not be abused, as they may lead to incorrect time complexity. See the "Complexity Analysis" section below.
Implementation (std::map)
Compared to the std::set implementation, the split operation in the std::map implementation is simpler. Other operations are no different from those in std::set.
Node Storage
Since the intervals stored in Chtholly Tree are continuous, we don't necessarily need to record the right endpoint. We can use a map<int, int> mp; to store all intervals, where the key maintains the left endpoint and its value maintains the value from that left endpoint up to (but not including) the next left endpoint.
During initialization, if the problem requires maintaining information from position \(1\) to \(n\), call mp[1] = -1, mp[n + 1] = -1 to indicate that \([1,n+1)\) i.e., \([1, n]\) are set to a special value \(-1\), and the interval \([n+1, +\infty)\) is used as a sentinel, which can also be initialized.
split Operation
Reference code: (First version)
1 2 3 4 | |
Reference code: (Second version)
1 2 3 4 5 | |
Here, we use the overload iterator insert( const_iterator pos, const value_type& value ); of std::map::insert, which inserts value as close as possible to the position just before pos. If the insertion occurs exactly at the position just before pos, the complexity is amortized constant; otherwise, it is logarithmic in the container size.
assign Operation
For the assign operation, we need to delete all interval left endpoints within \([l,r−1]\), then create a new interval.
1 2 3 4 5 6 7 8 9 | |
perform Operation
1 2 3 4 5 6 7 8 9 | |
Implementation (Linked List)
Currently, the mainstream implementation uses set to maintain nodes. However, since the average number of maintained intervals is small, the advantage of set is not significant. In contrast, linked lists (or arrays) can more concisely maintain split and merge operations.
Node Storage
1 2 3 4 5 6 7 8 9 10 11 12 | |
split Operation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
When operating on intervals, since we cannot maintain only part of an interval, the following operations need to pre-split the interval before performing the corresponding operation.
1 2 3 4 5 6 7 | |
assign Operation
1 2 3 4 5 6 7 8 | |
perform Operation
1 2 3 4 5 6 | |
Complexity Analysis
Calling assign on the same interval immediately after perform
Observing this case, we find that two split operations add at most two intervals; one assign deletes all intervals within the range and adds one interval, while traversing the deleted intervals. So, the number of intervals we traverse is linear with the number of deleted intervals, and each operation only adds \(O(1)\) intervals. Therefore, the number of intervals we operate on is linear with respect to the number of operations (including initialization). The time complexity is amortized \(O(m\log n)\), where \(m\) is the number of operations and \(n\) is the maximum number of intervals in the Chtholly Tree (we can consider \(n\leq m\)).
Not performing assign after perform
If special adversarial data is allowed, this can definitely be exploited. By creating enough different intervals in the Chtholly Tree and repeatedly traversing them, the complexity of Chtholly Tree can reach or even exceed quadratic.
To guarantee correct complexity, the data must be random. See Proof of Chtholly Tree complexity on Codeforces for details. A more detailed rigorous proof is in Complexity Analysis of Chtholly Tree. The conclusion of the proof is: the complexity of Chtholly Tree implemented with std::set is \(O(n \log \log n)\), while the complexity with a linked list is \(O(n \log n)\).
Exercises
- 「Luogu 1840」Color the Axis
「SCOI2010」Sequence Operation(Hack data has been added to this problem source)- 「SHOI2015」Brain Hole Treatment Device
- 「Luogu 4979」Mine Collapse
- 「Luogu 8146」risrqnis
Further Reading
Generalization of ODT's Mapping Idea - Luogu Column (luogu.com.cn)
References and Notes
- Problem - 896C - Codeforces (Origin of Chtholly Tree)
- CF896C Willem, Chtholly and Seniorious Solution - Luogu Column (luogu.com.cn) (
std::setimplementation reference) - Chtholly Tree Implementation with map - Zhihu (zhihu.com) (
std::mapimplementation reference) - Solution CF896C【Willem, Chtholly and Seniorious】- Luogu Column (luogu.com.cn) (Linked list implementation reference)
- Codeforces Round #449 Editorial - Codeforces (Proof of Chtholly Tree complexity)
- Complexity Analysis of Chtholly Tree - Zhihu (zhihu.com) (Complexity analysis of Chtholly Tree)
本页面最近更新:,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:OI-wiki
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用