致敬传奇出题人lxl
珂朵莉树起源于CF896C ,这道题要求我们实现一种数据结构,可以较快地实现区间和、区间赋值、求区间第 k 大值和求区间 n 次方和。
使用场景
珂朵莉树是一种技巧,而不是一种特定的数据结构,可以用map
实现也可以用set
等实现,适用于区间赋值操作的数据结构题,在数据随机的情况下复杂度极其优秀,但如果数据不随机且出题人针对数据结构构造了特殊数据,那么复杂度会严重退化。
结构实现(set)
struct Node_t { |
这里v
被声明为mutable
,这是因为在set
中,元素是const
的,无法修改。但我们需要在split
操作中修改v
的值,所以使用mutable
来解除const
限制。
核心操作-split
split
操作是珂朵莉树的核心,它的作用是将包含pos
位置的区间分裂成两个区间:[l, pos-1]
和[pos, r]
。这样我们就可以精确地对任意区间进行操作。
auto split(int pos) { |
s.lower_bound(Node_t{pos, 0, 0})
:找到第一个左端点 大于等于pos
的区间。if (it != s.end() && it->l == pos)
:如果找到了一个区间且这个区间的左端点就是pos
,那么我们无需分裂,直接返回该区间的迭代器。it--
:如果没找到,说明pos
在某个区间内部,我们需要回退一个迭代器,找到包含pos
的那个区间。s.erase(it)
:删除原来的大区间。s.insert(Node_t(l, pos - 1, v))
和s.insert(Node_t(pos, r, v))
:将被删除的区间分裂成两个小区间并插入回set
中。- 返回
pos
所在的新区间的迭代器。
核心操作-assign
assign
操作用于将区间[l, r]
的值全部修改为v
。这是珂朵莉树保证复杂度的关键操作,因为它能将多个小区间合并成一个大区间,从而减少set
中的元素数量。
void assign(int l, int r, int v) { |
auto itr = split(r + 1), itl = split(l);
:顺序很重要! 必须先split(r + 1)
再split(l)
。因为如果先split(l)
,返回的迭代器itl
可能会在split(r + 1)
时因为set
的修改而失效。通过split
操作,我们将区间[l, r]
独立出来。s.erase(itl, itr)
:删除[l, r]
范围内的所有旧区间。s.insert(Node_t(l, r, v))
:插入一个代表整个[l, r]
的新区间。
其他操作
在实现了split
和assign
之后,其他的区间操作就变得非常简单了。我们只需要利用split
将要操作的区间[l, r]
独立出来,然后遍历这个区间内的所有小块进行操作即可。
区间加
void add(int l, int r, int val) { |
查询区间第k小
int query_kth(int l, int r, int k) { |
查询区间幂次和
long long query_pow_sum(int l, int r, int p, int mod) { |
结构实现(map)
除了set
,我们也可以用map
来实现珂朵莉树。map
的key
可以存储区间的左端点l
,value
可以存储区间的右端点r
和值v
。
|
核心操作-split
map
版本的split
与set
版本类似,但操作map
的迭代器和元素的方式有所不同。
auto split(int pos) { |
核心操作-assign
assign
操作同样是先split
再合并。
void assign(int l, int r, int v) { |
其他操作
与set
版本类似,在map
版本中,我们同样利用split
函数来划定操作区间,然后遍历这个区间内的所有节点来完成相应操作。
区间加
void add(int l, int r, int val) { |
查询区间第k小
int query_kth(int l, int r, int k) { |
查询区间幂次和
long long query_pow_sum(int l, int r, int p, int mod) { |
复杂度分析
珂朵莉树的复杂度依赖于assign
操作的次数。在数据随机的情况下,assign
操作会使得区间数量大大减少,因此复杂度接近于O(n loglog n)
,其中n
是操作次数。然而,在最坏情况下(没有assign
操作),split
操作会不断增加区间的数量,复杂度会退化到O(n log n)
,甚至更差。
split(pos)
:O(log n)
,其中n
是set
中的元素数量。assign(l, r, v)
:O(k log n)
,其中k
是[l, r]
范围内的区间数量。由于assign
会合并区间,所以均摊复杂度很低。- 其他操作:
O(k log n)
,其中k
是[l, r]
范围内的区间数量。