珂朵莉树

珂朵莉树(ODT)

0x01 珂朵莉树可以解决什么问题

对于涉及区间推平操作的问题,就是把[l,r]区间内的所有数字变成相同的一个数。如果数据是随机的就可以使用珂朵莉树。

0x02 珂朵莉树的基本原理

比如一开始把一个区间分成这些部分,那么我们就可以用一个结构体将相邻的数字存起来,分别记录他们的始末点和这一段区间内的数字是多少。

struct Node { ll l, r;//l和r表示这一段的起点和终点 mutable ll v;//v表示这一段上所有元素相同的值是多少 Node(ll l, ll r = 0, ll v = 0) : l(l), r(r), v(v) {} bool operator<(const Node &a) const { return l < a.l;//规定按照每段的左端点排序 } };

关键字mutable 的意思是即使是一个常数,也允许修改v的值
将结构体存储在set里面按照左端点进行排序,就变成了下图的样子。

0x03 核心函数

split

因为我们要对一个区间内的所有数字进行操作,那么我们只需要将这个区间从原来的区间中取出来修改好了之后再放回去,就可以高效的实现区间推平操作了。
我们按照pos这个位置来进行分裂,将[l,r]区间分裂成[l,pos1],[pos,r]两个区间。如果pos本身就是一个区间的开头,那么就不需要去分割了直接返回这个区间就可以了。

set<Node>::iterator split(int pos) { set<Node>::iterator it = s.lower_bound(Node(pos)); if (it != s.end() && it->l == pos) { return it; } it--; if (it->r < pos) return s.end(); ll l = it->l; ll r = it->r; ll v = it->v; s.erase(it); s.insert(Node(l, pos - 1, v)); //insert函数返回pair,其中的first是新插入结点的迭代器 return s.insert(Node(pos, r, v)).first; }

将区间取出来之后,我们要确定我们找到的这个it是刚好是在以pos开头的这个区间,还是pos稍微大了一点点或者pos过大超出了最后一个区间。如果是第一种情况的话就可以直接返回这个区间,如果是后两种情况的话,可以先将it这个迭代器向前退一个单位,然后这个时候看看区间的右端点和pos的大小关系,如果小于pos的话,那么就说明pos太大了,直接返回s.end(),否则的话就将区间一分为二再重新插入回去。

assign

如图,我们将这个区间取出之后,将Node重新插入回去,就实现了。

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

当然,这个时候一定要先将区间的右端点取出来再将左端点取出来,否则可能会出现RE的情况。

add修改操作

就是将区间分裂之后,循环遍历一遍区间,将所有的数都加上x就好了。

void add(ll l, ll r, ll x) { set<Node>::iterator itr = split(r + 1), itl = split(l); for (set<Node>::iterator it = itl; it != itr; ++it) { it->v += x; } }

0x04整体的模板

struct ODT { struct Node { int64_t l, r; mutable int64_t v; Node (int64_t l, int64_t r = 0, int64_t v = 0) : l(l), r(r), v(v) {} bool operator < (const Node& lhs) const { return l < lhs.l; } }; std::set<Node> s; std::set<Node>::iterator split(int pos) { // 分裂区间 std::set<Node>::iterator it = s.lower_bound(Node(pos)); if (it -> l == pos && it != s.end()) return it; -- it; if (it -> r < pos) return s.end(); int64_t l = it -> l, r = it -> r, v = it -> v; s.erase(it); s.insert(Node(l, pos - 1, v)); return s.insert(Node(pos, r, v)).first; } void assign(int l, int r, int64_t x) { //区间推平 std::set<Node>::iterator itr = split(r + 1), itl = split(l); s.erase(itl, itr); s.insert(Node(l, r, x)); } void add(int64_t l, int64_t r, int64_t x) { //区间加法 std::set<Node>::iterator itr = split(r + 1), itl = split(l); for (auto it = itl; it != itr; it ++ ) it -> v += x; } struct Rank { int64_t val, cnt; bool operator < (const Rank& lhs) const { return val < lhs.val; } Rank(int64_t val, int64_t cnt) : val(val), cnt(cnt) {} }; int rank(int64_t l, int64_t r, int64_t x) { //查询区间排名为x的数是多少 std::set<Node>::iterator itr = split(r + 1), itl = split(l); std::vector<Rank> vec; for (auto it = itl; it != itr; ++ it ) vec.push_back(Rank(it -> v, it -> r - it -> l + 1)); std::sort(all(vec)); int64_t ans = -1; for (auto p : vec) { if (p.cnt < x) x -= p.cnt; else { ans = p.val; break; } } return ans; } int64_t query(int64_t l, int64_t r) { // 区间和 auto itr = split(r + 1), itl = split(l); int64_t ans = 0; for (auto it = itl; it != itr; ++ it) ans += (it -> v) * (it -> r - it -> l + 1); return ans; } };

__EOF__

本文作者HoneyGrey
本文链接https://www.cnblogs.com/Haven-/p/16553875.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   浅渊  阅读(55)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示