珂朵莉树ODT 浅析
Chtholly Tree
参考blog:
起源
珂朵莉树(又称Old Driver Tree,简称ODT或老司机树),源自于CF的一道比赛原题:CF896C Willem, Chtholly and Seniorious(因为题目背景是关于珂朵莉的),题意大概就是要求你维护一个神奇数据结构,维护一个具有$n$项的序列,具有如下操作$m$次:
- 将$[l,r]$区间所有数加上$x$
- 将$[l,r]$区间所有数改成$x$
- 输出$[l,r]$区间的第$k$小值
- 输出$[l,r]$区间的所有数的$x$次幂之和$(\sum_{i=1}^ra_i^x)\ mod \ y$
数据范围:$1\le n,m \le 10^5$,且保证所有数据随机(即保证1234操作出现的概率相同)
我们可以发现,前两项操作可能用普通线段树就能维护,第三项操作可以用主席树维护,而唯独第四个操作无法在可接受的时间复杂度范围内维护,但是题目说数据随机,因而此刻 珂朵莉树 就华丽登场了~ (面向题目编程)
PS:然而现在的出题人随随便便就能卡掉ODT
实现
珂朵莉树本质上是一种基于$STL$的$std::set$的一种暴力数据机构,适用场合一般具有如下特点:
- 保证数据随机
- 需要将一个区间的所有值改为同一个值(推平操作)
- 含有区间幂次等线段树、BIT无法胜任的操作时
珂朵莉树的每个节点存储的是一段值相同的区间,比如说序列:[1,2,2,2,3,4,4],在珂朵莉树内的存储方式就是:
L | R | V |
---|---|---|
1 | 1 | 1 |
2 | 4 | 2 |
5 | 5 | 3 |
6 | 7 | 4 |
需要注意的是,只有在数据随机的情况下,珂朵莉树中$set$所维护的区间大小才近似于$logn$,整体时间复杂度大致为$O(mlogn)$,然而如果数据不随机,那么这个复杂度就是假的,很可能会退化成暴力($O(m*n)$)的复杂度QWQ
ODT初始化
代码实现如下(珂朵莉树初始化):
1 |
|
接下来就是四个操作的实现了,在这之前,需要介绍一下ODT操作的核心:
$Split$操作
对于一整个区间,每次操作很可能是只需要修改区间的一部分,而另一部分不需要修改,这时,我们可以通过分割操作将区间分割开来,从而只修改需要修改的部分
1 |
|
有了Split操作后,一切都变得好办了
区间推平操作
先将ODT沿区间$[l,r]$切开,然后直接删除中间部分的集合(用$std::set$的$erase$方法,很冷门的方法),再直接插入一整块的相同值的区间集合
这里有个十分难以发现的关键点:一定要先切右端点,再切左端点,否则很可能会玄学RE(在这上面死了好几次)
1 |
|
区间加操作
暴力实现,先沿区间端点切开,然后直接遍历区间内的所有set元素(珂朵莉树节点),将其加上某值(即一整个区间加上某值)
1 |
|
询问区间第$k$小
暴力实现,先沿区间端点切开,然后直接遍历区间内所有珂朵莉树节点,将其存进一个$vector
1 |
|
求区间$[l,r]$的$(\sum_{i=l}^ra_i^x)\ mod\ y$
还是暴力实现(结合快速幂),类似于询问询问第$k$小,先切开后,遍历每一个节点,更新答案(加上 区间大小*该区间值的x次幂),累加最后返回即可
1 |
|
至此,珂朵莉树模板题的完整操作就介绍完毕了,下面奉上完整代码
code
1 |
|
习题
- CF915E Physical Education Lessons(动态开点、ODT)
动态开点code
1 |
|
ODT code
1 |
|
- CF343D Water Tree(树剖+线段树、树剖+ODT)
珂朵莉树最慢的点2s,线段树1s,本题时限4s(ODT水过了2333)
Segment Tree code
1 |
|
ODT code
1 |
|
很遗憾,此题用ODT会T掉最后一个点(因为有人造数据卡ODT),但是可以用来骗分(然而我已经是大学生了),正解是线段树
ODT code
1 |
|
Segment Tree code
1 |
|
- P5350 序列(FHQ Treap、ODT)
注意:此题保证数据随机,因此可以直接用ODT轻松水过(其实也算是一道ODT的模板题了)
千万要记住ODT的Split顺序是先大后小,用完一对指针后这对指针就废了,因此需要一个区间一个区间地处理
ODT code
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!