珂朵莉树
oi.ds.odt
upd.2025.7.15
珂朵莉树 (\(OldDriverTree\))
末日三问:
- 什么是珂朵莉树
- 珂朵莉树可以干什么
- 怎么写珂朵莉树
为什么叫珂朵莉树?
名字出处来源于Codeforces的一场比赛,因为题面和《末日三问》(《末日时在干什么?有没有空?可以来拯救吗?》)中的女主(其实并非,小说里上来就死了)珂朵莉相关,因此这道题的方法被称作珂朵莉树
珂朵莉树可以干什么?
经常用于处理带有“推平”(将某一段全部修改为一个数)类的询问
怎么写珂朵莉树?
瞎写
先来看一下模板题:
请你写一种奇怪的数据结构,支持:
\(1\) \(l\) \(r\) \(x\) :将\([l,r]\) 区间所有数加上\(x\)
\(2\) \(l\) \(r\) \(x\) :将\([l,r]\) 区间所有数改成\(x\)
\(3\) \(l\) \(r\) \(x\) :输出将\([l,r]\) 区间从小到大排序后的第\(x\)个数是的多少(即区间第\(x\)小,数字大小相同算多次,保证 \(1≤ x ≤ r−l+1\) )
\(4\) \(l\) \(r\) \(x\) \(y\) :输出\([l,r]\) 区间每个数字的\(x\) 次方的和模\(y\) 的值(即(\(\Sigma_{i=l}^ra_i^x\) \(mod\) \(y\)))
由于操作2,不难发现这道题中数据的共同点就是数值相同
我们先来看珂朵莉树是如何建的/*node为一个数据段*/
struct node {
int l, r; // 表示线段的左右端点
mutable int v; // 表示这一段每个元素的数值(1)
node(int l, int r = 0, int v = 0) :l(l),r(r),v(v){}
bool operator < (const struct node &A) const
{
return l < A.l; // 按照左端点排序
}
};
set\ s; // s就是一个珂朵莉树
(1):我们看到在定义v的时候使用了关键字
mutable
这个关键字可以确保v可以被修改,即使它在有些时候被识别成了常量,比如当你用set< node >::iterator去访问v的时候它依旧可以重新赋值
接下来我们去维护这个珂朵莉树
介绍维护操作中最重要 的操作:分割段
当我们想要使用操作1或操作2去修改数据时,就会出现需要把一段数据断开进行操作的时候,因此我们需要分割
//将数据段[l,r]分割为两端[l,pos-1],[pos,r];并且返回[pos,r] set::iterator split(int pos) { // 我们先查找pos这个数据在哪一段 // 定义时的重载 < 运算就是为了这里 // lower_bound是返回第一个l大于等于pos的段落,it或it前一段就是包含pos的段 auto it = s.lower_bound(node(pos)); // 假如说pos是这一段的开头第一个数据,那就不用分割了 if (it != s.end() && it->l == pos) return it; // 取it前一个数据 it --; // 假如pos是这一段的结尾也不用分割,返回空指针 if (pos > it->r) return s.end(); int l = it->l, r = it->r, v = it->v; // 先删再加回来 s.erase(it); s.insert(node(l,pos-1,v)); // insert的返回值是pair,first代表插入的数据 return s.insert(node(pos,r,v)).first; }
接下来介绍操作: 推平
void assign(int l, int r, int v)
{
// 注意:一定是要先split(r+1),否则会有概率RE
// 这行代码也是常用的操作,用于获得l开头的段落和r结尾的段落
set::iterator itr = split(r + 1), itl = split(l);
// 把l到r删掉
s.erase(itl, itr);
// 再加入推平后的段落
s.insert(node(l, r, v));
}
其实珂朵莉树就已经维护好了,使用珂朵莉树其实很简单,就是暴力拆段做,接下来拿操作4举例
//快速幂
int qpow(int a, int b, int p)
{
int res = 1;
a = a%p;
while (b)
{
if (b&1) res = res * a % p;
b >>= 1;
a = a * a % p;
}
return res % p;
}
int querypow(int l, int r, int x, int y)
{
//拆段
auto itr = split(r + 1), itl = split(l);
int s = 0;
for (auto it = itl; it != itr; it ++ )
// it这一段的贡献为 长度*每个数据的值
s += qpow(it->v,x,y)*(it->r - it->l + 1) % y, s %= y;
return s % y;
}
可见珂朵莉树其实并不是一棵树(也许是set帮你写好了),就是一种更为优雅的暴力,因此也被称为老司机树(Old Driver Tree)
中国珂学院链接:https://wiki.sukasuka.cn/首页
\(End.\)

浙公网安备 33010602011771号