珂朵莉树
珂朵莉树,ODT(Old Driver Tree),颜色段均摊都是它。其实我觉得硬说这是一种数据结构(尤其是“树”)是不恰当的,这更多应该是一种技巧。
与线段树等传统数据结构的区别在于:它可以更方便地维护每个被覆盖区间的值,因为我们认为对于一整个颜色段进行操作是容易的。如模板题中的操作 4:求 。
实现
一般用 std::set 实现。
节点
1 | |
和 表示这一段的区间, 表示这一段的权值,使用 mutable 修饰是为了使得结构体或函数在被 const 修饰后仍能修改 的值,这样,我们就可以直接修改在 set 内部的元素的 。
split
用于将一个区间为 的区间分裂为 和 ,并返回指向后者的迭代器的函数。
1 | |
std::set.insert() 返回一个 std::pair<iterator,bool>,表示插入元素的迭代器及插入是否成功。
现代编译器应当都可以将 auto 识别为 std::set<node>::iterator。
assign
用于区间赋值。同时也是时间复杂度的保证,以模板题为例,大约 的操作调用了 assign,而这个操作可以大幅减小 set 的大小。
特别注意:在截取 时一定要先调用 split(r+1) 再调用 split(l),否则可能导致 RE。 具体原因可以看这里。我大致解释一下,就是若删除了迭代器 it,就会导致 it 及以后的迭代器失效,此时若强行访问失效部分就是 UB。如果我们先操作前面再操作后面,itl 和 itr 之间包含的区间就可能不是我们想要的那一部分。由此我们就需要特别注意,**珂朵莉树的任何涉及 erase 的操作都要先操作后面再操作前面。**如区间 swap。
1 | |
模板题
对于 1 操作,3 操作和 4 操作,直接分离出对应区间后暴力求解。2 操作直接用 assign。
1 | |
习题
复杂度相关
我们可以从模板题中发现,这玩意除区间赋值外都需要暴力,所以是一种暴力数据结构。对于模板题,均摊时间复杂度 (关于这个题的复杂度说法很多,具体是不是这个我也不是很清楚,原题严谨的复杂度分析极其复杂,这里不展开分析)。其时间复杂度保证完全依赖于 assign 操作,所以仅适用于: