珂朵莉树/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 的实际段数很低,效率当然不错。

posted @ 2026-10-30 14:50  Rainesxiao  阅读(4)  评论(0)    收藏  举报