珂朵莉树学习笔记

0x00 什么是珂朵莉树#

所谓珂朵莉树,就是颜色段均摊。它并不是树形数据结构,而是线性数据结构

珂朵莉树的本质是合并一些有着相同信息的区间,以达到节省时间的效果。这里的信息一般是颜色,但也有例外。

珂朵莉树的修改与询问都是暴力,这使得珂朵莉树几乎无法处理区间赋值之外的区间修改或查询,但是也让珂朵莉树有非常广泛的用途。

0x01 常见的珂朵莉树写法#

珂朵莉树通常将区间转化为值,并使用 std::set 来维护每个区间,同一个区间里的值是相同的。代码如下:

struct node {
	int l, r; mutable int v;
	node(int L, int R = -1, int V = 0): l(L), r(R), v(V) {}
	bool operator < (const node& o) const { return l < o.l; }
};
typedef set<node>::iterator IT;
set<node> s;

代码中的 node 是维护了一个 [l,r] 的区间,这个区间里的数值全为 v

珂朵莉树有两个基本操作:

  • split 裂块
 IT split(int pos) {
	 IT it = s.lower_bound(node(pos));
	 if (it != s.end() && it->l == pos) return it;
	 int L = (-- it)->l, R = it->r, V = it->v;
	 s.erase(it), s.insert(node(L, pos - 1, V));
	 return s.insert(node(pos, R, V)).first;
 }
  • split 的作用是将 pos 所在的块 [l,r] 分裂为 [l,pos1],[pos,r] 两块,时间复杂度 O(logs)s 是当前 std::set 内区间的个数。

  • assign 区间赋值

void assign(int l, int r, int val = 0) {
    IT itr = split(r + 1), itl = split(l);
    s.erase(itl, itr);
    s.insert(node(l, r, val));
}
  • 随着 split,珂朵莉树中的区间个数会越来越多,导致复杂度爆炸,我们需要区间赋值操作来减少区间个数。区间赋值将 [l,r] 中的区间删除,重新合并为一段,时间复杂度 O(slogs)s 是删除的区间个数。
来模拟一下?

设原先区间是 [l1,r1],[l2,r2],,[ls,rs],且 l1lr1,lsrrs,代码中 assign 操作先是通过 split 将区间变成 [l1,l1],[l,r1],[l2,r2],,[ls,r],[r+1,rs];然后通过 erase,删除了 [l,r] 之间的所有区间,将区间变成 [l1,l1],[r+1,rs];最后插入区间 [l,r],将区间变成了 [l1,l1],[l,r],[r+1,rs],完成了区间赋值。

初始化只需插入 [1,1],[2,2],,[n,n] 即可。

0x02 珂朵莉树的复杂度与应用#

珂朵莉树复杂度是基于均摊的:

  • 初始插入 n 个区间,qassign 每次最多增加两个区间(两端的 split),总共最多出现 n+2q 个区间。
  • 每个区间只会被删除一次,故所有 splitassign 操作的总复杂度为 (n+2q)logn,均摊每次 O(logn)
  • 同时,如果珂朵莉树的区间询问与区间赋值绑定,则区间询问的时间复杂度也是 O(slogs),均摊 O(logn)

基于均摊,珂朵莉树可以以非常优秀的复杂度完成暴力才能维护的操作,这使得它有非常多的应用。例题:

LOJ #6284. 数列分块入门 8

区间询问等于一个数 c 的元素,并将这个区间的所有元素改为 c

区间询问与区间修改绑定,可以使用珂朵莉树暴力处理询问。时间复杂度 O((n+q)logn)

洛谷 T314507 猫猫的烟花易冷 2

区间赋值,区间查第 k 小数。

这题询问可与修改不绑定,好像与珂朵莉树没有任何关系!

先思考只有单点修改怎么做,这显然是 P2617 Dynamic Rankings。考虑这题的分块做法,维护每种颜色的块前缀和。观察一下,发现不仅支持单点修改,还支持区间同一颜色的修改。那么用珂朵莉树维护相同颜色的区间,assign 时对每个删除的区间修改前缀和即可。散块的话需要再用一个支持区间修改单点查询的分块维护。时间复杂度 O(n+qn)

类似地,用 P3332 [ZJOI2013] K大数查询 的树套树代替分块可以做到 O((n+q)log2n),不过笔者写了一下之后发现常数太大,反而跑不过分块。

随机数据下,珂朵莉树内的区间个数是 O(logn) 级别的。利用这一特性,珂朵莉树在不与区间赋值绑定的情况下,也可以进行区间操作。详细的证明可以参考这篇。例题:

CF896C Willem, Chtholly and Seniorious

区间加、区间赋值、区间查询 k 小值、区间查询 k 次方和。数据随机

直接跑暴力就做完了。

0x03 珂朵莉树的拓展#

维护的信息大多数情况是颜色,但也可以不是颜色。这类题和前面的珂朵莉树比较像的,也有搭配根号重构完全不像的。还是看例题吧:

[QwQOI2020] III

区间从小到大或从大到小排序,单点查询

用珂朵莉树维护每个排好序的区间(从小到大、从大到小),那么每次排序即推平。

每个区间维护一棵线段树,用线段树分裂、合并维护就行了。

时间复杂度 O(qlogn)

洛谷 P3391 【模板】文艺平衡树

q 次区间翻转,最后输出整个序列。

区间翻转,维护区间的话可以做到一次 O(s)s 是区间个数,每次最多会增加 2 个区间。

设立一个阈值 B,当 sB 时重构序列,这样 s 就重新变为 1

翻转一次 O(B),重构一次 O(n),总复杂度为 O(qB+nqB),取 B=n,时间复杂度为 O(qn)

例题 2 重构的思想还可以继续拓展,可以看很厉害的这篇:ODT的映射思想的推广。不过这篇后面讲的带插区间众数用珂朵莉树好像没有什么必要。

0x04 习题#

posted @   喵仔牛奶  阅读(189)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示
主题色彩