珂朵莉树/ODT
简介
珂朵莉树是一种利用平衡树(set,map)实现的暴力数据结构,用于处理区间操作
具体的说,珂朵莉树将序列分为多个段,区间操作时提取出区间内的段暴力遍历,至于复杂度的保证,则是依靠assign(合并)操作
详解(以模版题为例)
结构体
struct node{
int l,r;mutable ll v;
node(int L,int R=-1,ll x=0){l=L,r=R,v=x;}
bool operator<(const node& b) const{return l<b.l;}
};
l,r:段的左右端点 v:段的值,mutalbe作用是可以在set中直接修改,重定义就是保证set中的段是按顺序的
split
auto split(int p){
auto it=cth.lower_bound(p);
if(it!=cth.end()&&it->l==p) return it;
--it;
int l=it->l,r=it->r;ll v=it->v;
cth.erase(it),cth.insert(node(l,p-1,v));
return cth.insert(node(p,r,v)).first;
}
功能
将一个段在 \(p-1 \sim p\) 处切成两段,并返回右边段的指针
实现
先二分找到原段,特判一下是不是在最左侧(最左侧就不用切),然后直接删除原段再加两个新段即可
assign
void assign(int l,int r,ll v){
auto itr=split(r+1),itl=split(l);
cth.erase(itl,itr),cth.insert(node(l,r,v));
}
功能
将 \(l \sim r\) 合并成一个值为 \(v\) 的段
实现
先将左右不是整段的拆分出来,然后直接暴力删除+添加新段(注意是先split \(r+1\) 再split \(l\))
add,ranks,sum
void add(int l,int r,ll v){
auto itr=split(r+1),itl=split(l);
for(;itl!=itr;itl++) itl->v+=v;
}
ll ranks(int l,int r,int k){
vector<pair<ll,int>> s;
auto itr=split(r+1),itl=split(l);
for(;itl!=itr;itl++) s.push_back({itl->v,itl->r-itl->l+1});
sort(s.begin(),s.end());
for(auto [x,y]:s){
k-=y;
if(k<=0) return x;
}
}
ll sum(int l,int r,ll x,ll mod){
ll res=0;auto itr=split(r+1),itl=split(l);
for(;itl!=itr;itl++) res=(res+(itl->r-itl->l+1)*qpow(itl->v,x,mod)%mod)%mod;
return res;
}
功能
区间和,区间第 \(k\) 小,区间 \(k\) 次幂和
实现
原理相似,都是拆区间然后暴力遍历
复杂度
ODT 的做法看起来非常暴力,但是在随机数据的情况下,它的表现其实非常优秀。
我们可以证明,一个有 \(n\) 个数的序列,在经过 \(k\) 次随机的区间赋值操作后,期望段数大约在 \(O({n \over k} )\) 的级别。
证明可以点击 这里 查看,这里不再展开。
在模板题中,因为数据随机,有 \(1 \over 4\) 的操作均为随机的区间赋值操作,因此 ODT 的实际段数很低,效率当然不错。

浙公网安备 33010602011771号