珂朵莉树(ODT)

作用

珂朵莉树是一种基于 s e t set set 的高级暴力数据结构。在数据随机 且含有区间平推操作(将区间 [ l , r ] [l,r] [l,r] 中的数全部变为 x x x )的情况下,维护各种区间骚操作(包括但不限于查询区间第 k k k 大,查询 区间所有数 x x x 次方的和)。

实现

结构体

s e t set set 中的每一个元素的含义是数值相同的一段连续段。

struct node
{
	int l,r;
	mutable long long v;
	bool operator <(const node &x)const
	{
		return l<x.l;
	}
};

l , r l,r l,r 表示此元素代表的区间的左右端点, v v v 代表这段区间的数值, s e t set set 中每个元素按照区间左端点排序。
注意, v v v 前面的 m u t a b l e mutable mutable 不能省去,因为在之后的操作中,我们用迭代器 i t it it 访问到某一个元素,如果要修改 v v v 值时,因为 i t it it 实质上指代的是一个常量( c o n s t const const ),如果直接修改就会报错,但是加上 m u t a b l e mutable mutable (意为:可变的)后,就可以直接修改了。

关键操作: s p l i t split split(分裂)
set<node>::iterator split(int x)
{
	set<node>::iterator it=s.lower_bound((node){x,0,0});
	if(it!=s.end()&&it->l==x)
	return it;
	it--;
	int l=it->l,r=it->r;
	long long v=it->v;
	if(r<x)
	return s.end();
	s.erase(it);
	s.insert((node){l,x-1,v});
	return s.insert((node){x,r,v}).first;
}

这里 x x x 表示断点,我们先找到左端点大于等于 x x x 的区间,如果这个区间的左端点等于 x x x ,说明无需进行分裂,直接返回此段。反之使区间左移一个,如果此时区间右端点小于 x x x ,说明 x x x 在整个序列的外面,无需分裂,直接返回 e n d end end 。如果到这一步还没有返回,说明 x x x 在某个区间之中,我们将他分为 [ l , x − 1 ] , [ x , r ] [l,x-1],[x,r] [l,x1],[x,r] 两段,并返回 [ x , r ] [x,r] [x,r] 这一段的迭代器。
这里有个小细节, s e t set set i n s e r t insert insert 是有返回值的,返回值是 p a i r pair pair 类型, f i r s t first first 表示加入的这段区间的迭代器, s e c o n d second second 表示是否加入成功,我们在加入 [ x , r ] [x,r] [x,r] 时直接返回 i n s e r t insert insert f i r s t first first 即可。
为什么 s p l i t split split 要拥有返回值?
这里我们让 s p l i t split split 返回断点 x x x 断开后所在的段的迭代器(或 e n d end end ),是为了之后的各种操作方便,当我们要找到区间 [ l , r ] [l,r] [l,r] 中的所有元素时,我们可以直接用 s p l i t split split 断开 l , r l,r l,r 所在的整块后顺便找到左右端点所在的段的迭代器( s p l i t ( l ) , s p l i t ( r + 1 ) − − split(l),split(r+1)-- split(l),split(r+1)),方便遍历。

会了 s p l i t split split 操作之后,后面的就如履平地啦!

assign(推平)
void assign(int l,int r,int x)
{
	set<node>::iterator itr=split(r+1),itl=split(l);
	s.erase(itl,itr);
	s.insert((node){l,r,x});
}

找到并断开左右端点,删去代表区间 [ l , r ] [l,r] [l,r] 中元素的所有元素,加入推平后的整个大区间。
细节: s e t set set e r a s e erase erase 操作可以删除一段区间,具体的用法就是 e r a s e ( i t l , i t r ) erase(itl,itr) erase(itl,itr) i t l , i t r itl,itr itl,itr 是左右端点迭代器),表示删除 [ i t l , i t r ) [itl,itr) [itl,itr) 中的所有元素。
注意这里的推平操作要先 s p l i t ( r + 1 ) split(r+1) split(r+1) ,后 s p l i t ( l ) split(l) split(l) 。不然如果先 s p l i t ( l ) split(l) split(l) ,后面 s p l i t ( r + 1 ) split(r+1) split(r+1) 有可能会把 i t l itl itl 这一段给断开,从而造成 R E RE RE

add(增加)
void add(int l,int r,int x)
{
	set<node>::iterator itr=split(r+1),itl=split(l);
	for(;itl!=itr;itl++)
	itl->v+=x;
}

找到并断开左右端点,枚举其中的每一个区间,暴力增加( m u t a b l e mutable mutable 的作用在这里就体现出来了)。

其他的操作都是和 a d d add add 操作类似,直接怎么暴力怎么来就行。因为维护的每一段相当于同一个数,可拓展性极强,所以珂朵莉树可维护的区间操作也各种各样。

时间复杂度

经过一系列神奇的证明,我们可以知道在数据随机的情况下, s e t set set 内的期望块数为 l o g 2 n log_2n log2n ,所以 s p l i t split split a s s i g n assign assign a d d add add 等操作的时间复杂度都近似是 O ( l o g 2 n ) O(log_2n) O(log2n),跑的较快。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值