某科学的珂朵莉树
update
点击查看详情
| 时间 | 修改 |
|---|---|
| 2025.8.15 | 改正函数返回值错误 |
珂朵莉树概述
珂朵莉树又称老司机树(Chtholly Tree / Old Driver Tree)暴力数据结构,特别适用于处理区间赋值、区间修改等操作。该结构在随机数据下表现优异,尤其在处理大量区间操作时具有显著优势。
该数据结构最早在 Codeforces Round #394 (Div. 1) 的 C 题 CF-896C 中被提出,并因其题面中角色“珂朵莉”的名字而得名。在实际应用中,珂朵莉树常用于替代线段树或树状数组,在某些特定场景下实现更简洁、高效的代码逻辑。
前置知识
STL-set
简介
set 是 C++ STL 提供的标准关联容器,其核心特性包括元素唯一性和自动排序。底层基于红黑树(自平衡二叉搜索树)实现,这使得插入、删除和查找操作的时间复杂度均为 \(\Theta(\log n)\) ,能高效处理大规模数据。
迭代器 —— set <T>::iterator
set 的迭代器是 const_iterator,不允许通过迭代器修改元素值(排除使用 mutable)。通过 begin() 和 end() 可获取正向迭代器(指向首元素和尾后位置),rbegin() 和 rend() 获取反向迭代器(指向尾元素和首前位置)。迭代器在插入/删除操作后不会失效(红黑树仅调整指针,不移动内存)。 迭代器使用方式与普通指针相同。迭代器支持两种操作为 const_iterator++、const_iterator--,分别指向前驱和后继。
查找 —— `find()``
find(value) 函数用于查找元素 value,返回指向该元素的迭代器;若不存在,返回 end()。时间复杂度为 \(\Theta(\log n)\)。
示例:
set<int> s = {1, 3, 5};
auto it = s.find(3); // it 指向元素 3
if (it != s.end()) cout << *it; // 输出 3
插入 —— insert()
insert(value) 函数用于向 set 中插入元素 value,返回一个 pair<iterator, bool> 类型:
- iterator:指向插入元素的迭代器(若元素已存在,则指向原元素位置)。
- bool:插入成功标志(
true表示插入新元素,false表示元素已存在)。
时间复杂度为 \(\Theta(\log n)\)(红黑树节点插入需平衡调整,仅涉及指针操作)。
示例:
set<int> s;
// 插入新元素,返回 {迭代器, true}
auto res1 = s.insert(3);
if (res1.second) cout << "插入成功: " << *res1.first; // 输出 3
// 插入重复元素,返回 {原元素迭代器, false}
auto res2 = s.insert(3);
if (!res2.second) cout << "元素已存在,当前值: " << *res2.first; // 输出 3
删除 —— erase()
set 提供三种删除方式:
- 按值删除:
erase(value),删除所有值为value的元素(set 中最多一个),返回删除的元素个数(0 或 1)。 - 按迭代器删除:
erase(it),删除迭代器it指向的元素,无返回值。 - 区间删除:
erase(first, last),删除 [first, last) 区间内的元素,无返回值。
时间复杂度:均为 \(\Theta(\log n)\)(红黑树节点删除仅需指针调整)。
示例:
set<int> s = {2, 5, 8};
s.erase(5); // 删除值为 5 的元素,s 变为 {2, 8}
auto it = s.find(2);
s.erase(it); // 删除迭代器指向的元素,s 变为 {8}
规定大小查找 —— lower_bound()
lower_bound(value) 返回指向第一个大于等于 value 的元素的迭代器;若所有元素均小于 value,返回 end()。时间复杂度为 \(\Theta(\log n)\),常用于二分查找或区间划分。
示例:
set<int> s = {1, 4, 6, 9};
auto it = s.lower_bound(5); // 第一个 >=5 的元素是 6,it 指向 6
cout << *it; // 输出 6
正文
节点存储信息 —— ODT_node
珂朵莉树的每一个节点都存储了一段元素相同的区间,并把每一个节点按左端点从小到大扔进有序集即 set 中来方便操作。
struct ODT_node {
int l, r; // 区间的左右端点
mutable int val; // 区间的取值(由于可能修改所以使用 mutable)
bool operator < (const ODT_node x) const {return l < x.l;}
// 为使用是 set 需要重载运算符并按左端点排序
};
建树操作 —— void build(int a[], int n)
void build(int a[], int n) 表示建一棵表示 \(a\) 数组的树。具体为找出一段连续的值用一个节点表示。
void build(int a[], int n) {
int x = a[1], pos = 1;
// x 为值,pos 为一段连续的值的左端点
for (int i = 2; i <= n; i++) {
if (a[i] != x) {
t.insert({pos, i - 1, x});
pos = i; x = a[i];
// 找到不同的位置,插入节点
}
}
t.insert({pos, n, a[n]});
// 插入剩下的元素
}
查找节点操作 —— set<ODT_node>::iterator find(int x)
set<ODT_node>::iterator find(int x) 表示查找包含 \(x\) 的节点,并返回指向该节点的指针。
set<ODT_node>::iterator find(int x) {
auto it = t.lower_bound({x, 0, 0});
// 找出要分裂的节点的下一个节点
if (it == t.end() || it->l > x) it--;
// 找出要分裂的节点
return it;
}
节点分裂操作 —— set<ODT_node>::iterator split(int x)
set<ODT_node>::iterator split(int x) 表示把一个包含 \(x\) 的节点按 \(x\) 分裂成两个节点并返回分裂后靠右节点的指针,如下:
具体过程为先二分得出需要分裂的节点,然后删除要分裂的节点,插入分裂后的两个节点。
set<ODT_node>::iterator split(int x) {
auto it = t.find(x);
// 找出要分裂的节点
if (it->l == x) return it;
// 如果要分裂的节点左端点就是 x,则不需要分裂
int l = it->l, r = it->r, val = it->val;
// 记录信息,防止之后的操作改变指针
t.erase(it); t.insert({l, x - 1, val});
return t.insert({x, r, val}).first;
// 删除要分裂的节点,插入分裂后的两个节点
}
区间合并操作 —— set<ODT_node>::iterator merge(set<ODT_node>::iterator it, bool dir)
set<ODT_node>::iterator merge(set<ODT_node>::iterator it, bool dir) 表示把一 \(it\) 左右两边的与它相同的区间合并并返回合并后的指针,\(dir = 1\) 合并左边,反之,如图:
set<ODT_node>::iterator merge(set<ODT_node>::iterator it, bool dir) {
if (it == t.end()) return t.end();
// 如果 it 指向的是最后一个节点,则不需要合并
// 合并左边
if (dir) {
if (it == t.begin()) return t.end();
// 如果 it 指向的是第一个节点,则不需要合并
auto prev_it = prev(it);
// 左边的指针
if (prev_it->val == it->val) {
int l = prev_it->l, r = it->r, val = it->val;
// 预先记录信息
it = t.erase(prev_it);
if (it != t.end()) t.erase(it);
// 删除原节点
return t.insert({l, r, val}).first;
// 添加合并后的节点
}
return it;
}
// 合并右边(同上)
auto next_it = next(it);
if (next_it == t.end()) return t.end();
if (next_it->val == it->val) {
int l = it->l, r = next_it->r, val = it->val;
t.erase(it);
if (next_it != t.end()) t.erase(next_it);
return t.insert({l, r, val}).first;
}
return it;
}
区间推平操作 —— void assign(int l, int r, int val)
void assign(int L, int R, int Val) 表示把 \(L\) 到 \(R\) 区间内的数赋值为 \(Val\),如下:
具体过程为在 \(L\) 和 \(R + 1\) 处分裂,删除中间的一段被赋值的区间的原节点,最后重新插入被赋值的区间。
void assign(int l, int r, int val) {
auto ir = split(r + 1), il = split(l);
// 分裂操作端点(必须先分裂 r 再分裂 l 不然迭代器会失效)
t.erase(il, ir);
// 删除中间的一段被赋值的区间的原节点
t.insert({l, r, val});
// 重新插入被赋值的区间
it = merge(it, 1); merge(it, 0);
// 合并端点
}
其他的区间修改操作 —— void fun(int l, int r, ……)
珂朵莉树修改操作都可以按一下模板操作(类比分块)。
在 \(L\) 和 \(R + 1\) 处分裂,修改中间的一段被操作的区间的节点。
void fun(int l, int r, ……) {
auto ir = split(r + 1), il = split(l);
// 分裂操作端点
for (auto it = il; it != ir; it ++ )
// 对中间的节点操作
merge(find(l), 1); merge(find(r), 0);
// 合并端点
}
区间查询操作 —— int fun(int l, int r)
珂朵莉树查询操作都可以按一下模板操作(y也类比分块)。
在 \(L\) 和 \(R + 1\) 处分裂,计算中间的一段被查询的区间的贡献。
int fun(int l, int r) {
auto ir = split(r + 1), il = split(l);
// 分裂操作端点
for (auto it = il; it != ir; it ++ )
// 对中间的节点贡献
merge(find(l), 1); merge(find(r), 0);
// 合并端点
}
total-code
struct ODT_node {
int l, r;
mutable int val;
bool operator < (const ODT_node x) const {return l < x.l;}
};
struct ODT {
set<ODT_node> t;
void build(int a[], int n) {
int x = a[1], pos = 1;
for (int i = 2; i <= n; i++) {
if (a[i] != x) {
t.insert({pos, i - 1, x});
pos = i; x = a[i];
}
}
t.insert({pos, n, a[n]});
}
set<ODT_node>::iterator find(int x) {
auto it = t.lower_bound({x, 0, 0});
if (it == t.end() || it->l > x) it--;
return it;
}
set<ODT_node>::iterator split(int x) {
auto it = find(x);
if (it->l == x) return it;
int l = it->l, r = it->r, val = it->val;
t.erase(it); t.insert({l, x - 1, val});
return t.insert({x, r, val}).first;
}
set<ODT_node>::iterator merge(set<ODT_node>::iterator it, bool dir) {
if (it == t.end()) return t.end();
if (dir) {
if (it == t.begin()) return t.end();
auto prev_it = prev(it);
if (prev_it->val == it->val) {
int l = prev_it->l, r = it->r, val = it->val;
it = t.erase(prev_it);
if (it != t.end()) t.erase(it);
return t.insert({l, r, val}).first;
}
return it;
}
auto next_it = next(it);
if (next_it == t.end()) return t.end();
if (next_it->val == it->val) {
int l = it->l, r = next_it->r, val = it->val;
t.erase(it);
if (next_it != t.end()) t.erase(next_it);
return t.insert({l, r, val}).first;
}
return it;
}
void assign(int l, int r, int val) {
auto ir = split(r + 1), il = split(l);
t.erase(il, ir);
auto it = t.insert({l, r, val}).first;
it = merge(it, 1); merge(it, 0);
}
void fun(int l, int r, ……) {
auto ir = split(r + 1), il = split(l);
for (auto it = il; it != ir; it ++ )
// 对中间的节点操作
merge(find(l), 1); merge(find(r), 0);
}
int fun(int l, int r) {
auto ir = split(r + 1), il = split(l);
for (auto it = il; it != ir; it ++ )
// 对中间的节点贡献
merge(find(l), 1); merge(find(r), 0);
}
};

浙公网安备 33010602011771号