作用
珂朵莉树是一种基于 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,x−1],[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),跑的较快。