珂朵莉树 学习笔记

珂朵莉树可以做维护带有区间赋值的数据结构题。事实上,珂朵莉树不是树,甚至不是数据结构,本质是基于数据随机的颜色段均摊。

建树

珂朵莉树将数值相同的区间看做整体,用 set 维护。

节点的保存方式:

template<typename T>struct node{
  int l,r;
  mutable T v;
  bool operator<(node a){
    return l<a.l;
  }
};

\(l,r\) 为区间端点,\(v\) 为区间值。

mutable 关键字表示 \(v\) 可变,这样可以直接修改 set 内部的值。不加就会 CE。

建树

每个数单独一段扔进 set。

void build(int n,T a[]){
  for(int i=1;i<=n;i++)tr.insert(node<T>{i,i,a[i]});
}

split操作

当操作区间端点落在区间中,需要 split 操作可以将区间断开,返回后面区间的迭代器。

首先二分第一个大于等断点的区间。如果断点刚好在这个区间上则不用断开,直接返回。否则找到上一个区间。

如果当前区间的右端点小于断点,说明上一步二分出来的迭代器是 tr.end(),断点越界。否则执行分裂操作。

代码:

typename set<node<T>>::iterator split(int pos){
  typename set<node<T>>::iterator it=tr.lower_bound(node<T>{pos,0,0});
  if(it!=tr.end()&&(*it).l==pos)return it;
  it--;
  if((*it).r<pos)return tr.end();
  int l=(*it).l,r=(*it).r;
  T v=(*it).v;
  return tr.erase(it),tr.insert(node<T>{l,pos-1,v}),tr.insert(node<T>{pos,r,v}).first;
}

区间赋值

对区间两端执行分裂操作,删掉中间的部分,插入一整段区间。

代码:

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

这个操作比较特殊,因为只有它能使段数下降。如果赋值操作过少或没有,段数过多,复杂度就无法保证。因此多数时候珂朵莉树复杂度不一定正确,需要随机数据以保险,此时期望复杂度 \(O(\log^2n)\) 的。数据不随机时,珂朵莉树基本不会是正解。

其他操作

把区间 split 出来,枚举每一段暴力操作。

以区间加为例:

void add(int l,int r,T x){
  typename set<node<T>>::iterator itr=split(r+1),itl=split(l);
  for(typename set<node<T>>::iterator it=itl;it!=itr;it++)(*it).v+=x;
}

此处必须先 split 右端,再 split 左端。当两个端点位于同一个区间,split 左端点返回的迭代器会在 split 右端点时删掉,就无效了,有极小概率 RE。

[[数据结构]]

posted @ 2024-03-01 09:29  lgh_2009  阅读(14)  评论(0)    收藏  举报