珂朵莉树/颜色均摊

本文是结合OI WiKi来讲解
名称由来:
珂朵莉树,又名老司机树 ODT(Old Driver Tree)。起源自 CF896C。
主要用途:
用于区间赋值操作的数据结构题。在数据随机的情况下一般效率较高,由于其本身是一种暴力求解,因此会被精心构造的特殊数据卡到超时。
实现方式:
为了更好地理解,我们引入例题来进行讲解:由 n n n个物品,进行 m m m次染色,求 m m m次染色之后,最终物品有哪几种颜色?
1、节点的保存方式:

struct Node_t {
  int l, r;
  mutable int v;

  Node_t(const int &il, const int &ir, const int &iv) : l(il), r(ir), v(iv) {}

  bool operator<(const Node_t &o) const { return l < o.l; }
};

节点信息:

  • l 、 r 、 v l、r、v lrv 表示:第 l l l ~ r r r 件物品被染成了 v v v
  • mutable 关键字的含义:被 mutable 修饰的变量(mutable 只能用于修饰类中的非静态数据成员),将永远处于可变的状态,即使在一个 const 函数中。

2、 s p l i t split split 函数:
s p l i t split split 是最核心的操作之一,它用于将原本包含点 x 的区间(设为 [l, r])分裂为两个区间 [l, x) 和 [x, r] 并返回指向后者的迭代器。
在进行分裂之前,我们首先要找到 x x x 所在的线段,由于所有的区间都存储到 s e t set set 中,于是我们可以借助 s e t set set 中的查找函数 u p p e r upper upper_ b o u n d bound bound ,其返回的指向是 s e t set set 内区间左端点大于 x x x 的迭代器(简单理解为地址),将其自减得到的就是包含 x x x 区间的迭代器。
接下来就是进行区间分裂,如果此时 x x x 就是区间的左端点,那我们就不需要进行分裂,直接返回迭代器的位置就行了,如图:
在这里插入图片描述
倘若, x x x 不是左端点该如何处理?那直接删除 x x x 所在的区间,然后插入区间 [ l , x − 1 ] 、 [ x , r ] [l,x-1]、[x,r] [l,x1][x,r] ,那就产生了一个新的问题,我们如何返回 x x x 所在区间的迭代器?其实 s e t set set i n s e r t insert insert 函数会返回一个 p a i r pair pair 结构体,其中 p a i r 中的 f i r s t pair中的first pair中的first 就是 x x x 所在位置的迭代器,直接返回即可。
在这里插入图片描述

set<Node_t>::iterator split(int x) 
{
  if (x > n) return odt.end();
  auto it = --odt.upper_bound(Node_t{x, 0, 0});
  if (it->l == x) return it;
  int l = it->l, r = it->r, v = it->v;
  odt.erase(it);
  odt.insert(Node_t(l, x - 1, v));
  return odt.insert(Node_t(x, r, v)).first;
}

3、 a s s i g n assign assign 函数
操作 a s s i g n assign assign 用于对一段区间进行赋值。 对于 ODT 来说,区间操作只有这个比较特殊,也是保证复杂度的关键。
假设现在我们需要对区间 [ l 、 r ] [l、r] [lr] 进行染色,如图:
在这里插入图片描述
因此需要将 L 、 R L、R LR 与其之前的区间断开,这个时候我们就需要用到 s p l i t split split 函数,将其断开之后,使用 e r a s e erase erase 暴力的删除它们之前的这段区间,然后在 s e t set set 中添加区间 [ L 、 R ] [L、R] [LR]
注意:

  • 由于 e r a s e erase erase 删除的区间是前闭后开,所以我们要 s p l i t ( R + 1 ) split(R+1) split(R+1)
  • 珂朵莉树在进行求取区间左右端点操作时,必须先 split 右端点,再 split 左端点。若先 split 左端点,返回的迭代器可能在 split 右端点的时候失效,可能会导致 RE。
    在这里插入图片描述
void assign(int l, int r, int v) 
{
  auto itr = split(r + 1), itl = split(l);	//注意要先分裂R+1
  odt.erase(itl, itr);
  odt.insert(Node_t(l, r, v));
}

4、其它操作:
套模板就好了,参考代码如下:

void performance(int l, int r) 
{
  auto itr = split(r + 1), itl = split(l);
  for (auto it=itl; it != itr; ++it) 
  {
    、、、
  }
}

注意:

  • 开始的时候,我们需要将全部区间添加进 s e t set set 中。
	set<Node_t> odt;
	odt.insert({1,N,-1});
  • 在使用珂朵莉树的时候,往往需要观察一些性质,减少一些其它操作,避免过高的时间复杂度,例如:Physical Education Lessons
  • 32
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

离你很远的地方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值