珂朵莉树学习笔记
还有三周就是 NOIp 复赛了,我还在这学些乱七八糟的东西怕不是要凉。
话说上面那个数字是 3 你们看的出来吗,反正我在我的编辑器内看不出来。
总之就是,我们机房的一位珂学家突然沉迷数据结构并不知道从哪翻出来的毒瘤数据结构。
我们先来看一道毒瘤例题,「CF896C」Willem, Chtholly and Seniorious。
如果只看前三个操作的话,虽然我不会写也没写过,我觉得分块大概是可以做的。然而操作四是个什么鬼?区间 $10^{9}$ 次方和?认真的?
这种时候我们就需要用到珂朵莉树了。珂朵莉树的英文名叫 Old Driver Tree,简称 ODT。当然您也可以叫它 Chtholly Tree。从本质上来讲,珂朵莉树是一种基于 std::set
的暴力数据结构,虽然您也可以手写一颗平衡树,它的高效几乎全部建立在数据的完全随机上。如果您看过一些其他的珂朵莉树的学习笔记或者是题解的话,您应该总是会看到这样一句话:
珂朵莉树的核心操作在于推平一段区间,使一整段区间内的东西变得一样。
那么怎么推平呢?不急,我们慢慢讲。
珂朵莉树存储的是区间,而且每个区间内的所有值都相同,也就是说,我们需要存储的是一个区间的左右端点和值。我们可以写一个结构体:
1 | struct node{ |
然后我们就可以初始化了。对于这道题,我们可以像这样初始化:
1 | for(re int i=1;i<=n;++i) |
初始化完了?
初始化完了。
然后就是珂朵莉树的核心操作之一,
1 | std::<set>::iterator split(int pos) |
作用是分离出一个左端点为 pos
的区间并返回它的迭代器。它的具体实现如下:
1 |
|
虽然只是把一个区间砍成两半又放回去了,但我们并不是在做无用功,因为我们把原区间与操作无关的部分分离开了。
insert
函数的返回值类型是 std::pair<std::_Rb_tree_const_iterator<node>,bool>
,别问我啥意思我也不懂,我只是从它的错误报告里抄过来了,因此我们需要加个 .first
。
std::set
内部是用红黑树实现的,虽然我没写过,不过据说,它每次操作的时间复杂度都是近似 $O(\operatorname{log}n)$ 的。
然后是一个同样很核心的操作,
1 | void assign(int l,int r,int x) |
将区间 $[l,r]$ 推平,全部赋成 $x$。它的具体实现如下:
1 | inline void assign(int l,int r,int x){ |
这就完了?
这就完了。
assign
操作保证了珂朵莉树的时间复杂度,在数据随机的情况下,会使得 set
的大小迅速减小,并最终趋于 $\operatorname{log}n$。本题的操作二就只需调用一次 assign(l,r,x)
即可。
然后是剩下的三个操作,一个比一个暴力。
操作一,一个一个区间地拿出来加。
1 | inline void IntervalAdd(int l,int r,int x){ |
就这么几行?
就这么几行。
操作三,把所有区间取出来,然后直接调用 std::sort
。
1 | inline long long IntervalXth(int l,int r,int x){ |
操作四,暴力快速幂。
1 | inline int IntervalXpow(int l,int r,int x,int y){ |
您可能会想,这么暴力的东西,时间复杂度确定不会动不动原地起爆?
这是我在 CodeForces 上的提交记录:
这是我之前提到的那位不愿意透露姓名的珂学家的跑得最快的一次提交记录:
珂幻 .jpg
因为珂朵莉树的高效几乎全部建立在数据的完全随机上,能用珂朵莉树做的题并没有多少,大部分是在做正解是线段树之类的题想不出正解时骗分用。就比如说,我在学习珂朵莉树时参考的一篇题解提到了「CF915E」Physical Education Lessons,然而……
Updated on 2018-11-01
后来我卡了卡常数过了……
以及,删掉了某些内容(
于是,接下来是珂朵莉树的完整板子。
1 |
|
以上。