首页 > 其他分享 >【模板】珂朵莉树

【模板】珂朵莉树

时间:2024-08-02 21:20:20浏览次数:15  
标签:std set int 朵莉树 split 区间 模板

这是即将推送到 OI-wiki 的,对于原 OI-wiki 珂朵莉树页面的重构。作者是我。感谢上一位维护这玩意的人。

简介

珂朵莉树(Chtholly Tree),又名老司机树 ODT(Old Driver Tree)。起源自 CF896C

这个名称指代的是一种“使用平衡树(std::setstd::map 等)或链表(std::list、手写链表等)维护颜色段均摊”的技巧,而不是一种特定的数据结构。其核心思想是将值相同的一段区间合并成一个结点处理。相较于传统的线段树等数据结构,对于含有区间覆盖的操作的问题,珂朵莉树可以更加方便地维护每个被覆盖区间的值。

实现(std::set)

结点类型

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; }
};

其中,int v 是你自己指定的附加数据。

???+ note "mutable 关键字的含义是什么?"
mutable 的意思是「可变的」,让我们可以在后面的操作中修改 v 的值。在 C++ 中,mutable 是为了突破 const 的限制而设置的。被 mutable 修饰的变量(mutable 只能用于修饰类中的非静态数据成员),将永远处于可变的状态,即使在一个 const 函数中。

这意味着,我们可以直接修改已经插入 `set` 的元素的 `v` 值,而不用将该元素取出后重新加入 `set`。

结点存储

我们希望维护所有结点,使得这些结点所代表的区间左端点单调增加且两两不交,最好可以保证所有区间的并是一个极大的连续范围。此处以 std::set 为例,用一个 set<Node_t> odt; 维护所有结点。

初始化时。向珂朵莉树中插入一个极长区间(如题目要求维护位置 \(1\) 到 \(n\) 的信息,插入区间 \([1,n+1]\))。

split 操作

split 操作是珂朵莉树的核心。它接受一个位置 \(x\),将原本包含点 \(x\) 的区间(设为 \([l, r]\))分裂为两个区间 \([l, x)\) 和 \([x, r]\),并返回指向后者的迭代器。

参考代码如下:

auto split(int x) {
  auto it = odt.lower_bound(Node_t(x, 0, 0));
  if (it != odt.end() && it->l == x) return it;
  --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;
}

assign 操作

另外一个重要的操作:assign。用于对一段区间进行赋值。设将要对区间 \([l,r]\) 赋值为 \(v\)。

首先,将区间 \([l, r]\) 截取出来。依次调用 \(split(r+1), split(l)\),将此两者返回的迭代器记作 \(itr, itl\),那么 \([itl, itr)\) 这个迭代器范围就指向了珂朵莉树中 \([l,r]\) 包含的所有区间。

然后,将原有的信息删除。std::set 有成员方法 erase,签名如同 iterator erase( const_iterator first, const_iterator last );,可以移除范围 [first; last) 中的元素。于是我们调用 odt.erase(itl, itr); 以删除原有的信息。

最后,插入区间 \([l,r]\) 的新值。调用 odt.insert(Node_t(l, r, v)) 即可。

参考代码如下:

void assign(int l, int r, int v) {
  auto itr = split(r + 1), itl = split(l);
  odt.erase(itl, itr);
  odt.insert(Node_t(l, r, v));
}

??? 为什么需要先 split(r + 1)split(l)

1. `std::set::erase` 方法将使指向被擦除元素的引用和迭代器失效。而其他引用和迭代器不受影响。
2. `std::set::insert` 方法不会使任何迭代器或引用失效。
3. `split` 操作会将区间拆开。调用 `split(r + 1)` 之后 $r + 1$ 会成为两个新区间中右边区间的左端点,此时 `split` 左区间,必然不会访问到 $r + 1$ 为左端点的那个区间,也就不会将其拆开,删去 $r + 1$ 为左端点的区间,使迭代器失效。反之,先 `split(l)`,再 `split(r + 1)`,可能会把 $l$ 为左端点的区间删去,使迭代器失效。

perform 操作

将珂朵莉树上的一段区间提取出来并进行操作。与 assign 操作类似,只不过是将删除区间改为遍历区间。

参考代码如下:

void perform(int l, int r) {
  auto itr = split(r + 1), itl = split(l);
  for (; itl != itr; ++itl) {
    // Perform Operations here
  }
}

注意不应该滥用这样的提取操作,可能使得时间复杂度错误。见下文“复杂度分析”一栏。

实现(std::map)

相较于 std::set 的实现,std::map 的实现的 split 操作写法更简单。除此之外,其余操作与 std::set 并无二异。

结点存储

由于珂朵莉树存储的区间是连续的,我们不一定要记下右端点是什么。不妨使用一个 map<int, int> mp; 存储所有区间,其键维护左端点,其值维护其对应的左端点到下一个左端点之前的值。

初始化时,如题目要求维护位置 \(1\) 到 \(n\) 的信息,则调用 mp[1] = -1, mp[n + 1] = -1 表示将 \([1,n+1)\) 即 \([1, n]\) 都设为特殊值 \(-1\),\([n+1, +\infty)\) 这个区间当作哨兵使用,也可以对它进行初始化。

split 操作

参考代码:(第一份)

void split(int x) {
  auto it = prev(mp.upper_bound(x)); // 找到左端点小于等于 x 的区间。
  mp[x] = it->second; // 设立新的区间,并将上一个区间储存的值复制给本区间。
}

参考代码:(第二份)

auto split(int pos) {
  auto it = prev(mp.upper_bound(pos)); // 找到左端点小于等于 x 的区间。
  return mp.insert(it, make_pair(pos, it->second)); // 设立新的区间,并将上一个区间储存的值复制给本区间。
}

这里使用了 std::map::insert 的重载 iterator insert( const_iterator pos, const value_type& value );,其插入 value 到尽可能接近正好在 pos 之前的位置。如果插入恰好发生在正好在 pos 之前的位置,那么复杂度是均摊常数,否则复杂度与容器大小成对数。

其余操作与 std::set 并无二异。

实现(链表)

可参考 题解 CF896C 【Willem, Chtholly and Seniorious】 - 洛谷专栏 (luogu.com.cn)

复杂度分析

perform 以后立即对同一区间调用 assign

此时观察发现,两次 split 操作至多增加两个区间;一次 assign 将删除范围内的所有区间并增加一个区间,同时遍历所删除的区间。所以,我们所遍历的区间与所删除的区间数量成线性,而每次操作都只会增加 \(O(1)\) 个区间,所以我们操作的区间数量关于操作次数(包括初始化)成线性,时间复杂度为均摊 \(O(m\log n)\),其中记 \(m\) 为操作次数,\(n\) 为珂朵莉树中最大区间个数(可以认为 \(n\leq m\))。

perform 以后不进行 assign

如果允许特殊构造数据,这样一定是能被卡掉的,只需要使珂朵莉树中有足够多的不同区间并反复遍历,就能使珂朵莉树的复杂度达到甚至高于平方级别。

如果要保证复杂度正确,必须保证数据随机。详见 Codeforces 上关于珂朵莉树的复杂度的证明。更详细的严格证明见 珂朵莉树的复杂度分析。证明的结论是:用 std::set 实现的珂朵莉树的复杂度为 \(O(n \log \log n)\),而用链表实现的复杂度为 \(O(n \log n)\)。

习题

扩展阅读

ODT的映射思想的推广 - 洛谷专栏 (luogu.com.cn)

标签:std,set,int,朵莉树,split,区间,模板
From: https://www.cnblogs.com/caijianhong/p/18339590

相关文章

  • 【笔记】模板整理以及警钟长鸣
    图论部分\(\text{I}\).连通性部分有向图强连通分量\(\text{(SCC)}\)代码模板#include<bits/stdc++.h>usingnamespacestd;constintN=1e4+5;intn,m,num,scc_cnt,top;boolinstk[N];intdfn[N],low[N],stk[N],blg[N];vector<int>g[N],ans_scc[N],ne......
  • 苹果cmsv10酷黑模板模板 视频网站源码自适应模板【带有广告位】
    探索苹果CMSV10酷黑模板:打造高端自适应视频网站的完美选择在当今数字化时代,视频网站已成为人们休闲娱乐、获取信息的重要渠道之一。而一个精美、高效且用户友好的视频网站模板,则是吸引访客、提升用户体验的关键。今天,我们将带您深入了解苹果CMSV10的酷黑模板,这款集美观性、......
  • D35【模板】2-SAT
    视频链接:D35【模板】2-SAT_哔哩哔哩_bilibili   D14强连通分量Tarjan算法-董晓-博客园(cnblogs.com)P4782【模板】2-SAT-洛谷|计算机科学教育新生态(luogu.com.cn)//2-SAT+tarjanO(n+m)#include<iostream>#include<cstring>#include<algorithm......
  • 转转交易猫自带客服多模板全开源完整定制版源码
    转转交易猫自带客服多模板全开源完整定制版源码。请在后台商品添加成功后,再点击该商品管理,可重新编辑当前商品的所有信息及配图以及支付等等相关信息可点击分享或者跳转,将链接地址进行发布分享请在手机端打开访问访问商品主要模板文件路径目录咸鱼;http://你的域名地址/Xia......
  • 易优CMS模板标签switch条件判断支持多条件判断
    【基础用法】标签:switch描述:简单条件判断,比if判断标签少些不等于相同功能,视个人习惯而用。用法:{eyou:switchname='$eyou.field.has_children'}{eyou:casevalue='1'}当前栏目列表的栏目ID有1个下级栏目{/eyou:case}{eyou:casevalue='2'}当前栏目列表的栏目ID有2个下级栏目{/e......
  • 易优CMS模板标签relevarticle相关文档
    [基础用法]标签:relevarticle描述:通过前3个TAG标签或前3个关键词,检索整站文档标题中含有tag标签或者关键词的相关文档,进行关联。在没有tag标签情况下,就以前3个关键词检索文档标题进行关联。这个标签随着数据量的增加可能会比较影响检索性能。提示:使用该标签之前,必须先安装相关文......
  • 模板方法模式
    上层抽象类定义好操作的基本框架,一些特殊的子操作交给子类去实现,使得子类可以在不改变上层基类的情况下,可以定制操作的某一步骤。抽象类:模版方法:定义操作的骨架基本方法抽象方法:交给子类实现具体方法:基类自己实现,子类也可以进行覆盖(重写)具体实现类实现......
  • 用selenium打开网页的最小模板
    前言环境:win10python3.10selenium4.12经常用selenium来实现一个打开网页的这样一个小功能,虽然代码很少,但每次重0开始写就很烦。所以这里记录下一个模板小模板importtimefromseleniumimportwebdriverfromselenium.webdriver.common.byimportByfromselenium.web......
  • Smartforms在同一页中打印多份模板,打印动态表格
    要求:一:需要在一份A4纸中打印上下两个表格,每个表格行只有5行,不够的需要补齐,超出的需要打印到第二个表格中.二:表格不是固定的,需要根据某个字段,确定使用的表格格式 解决方法,我们只需要创建一个模板高度的数据模板,通过循环来打印我们的模板,相当于每次印半页,印两次就......
  • 在AWS Lightsail建立WordPress Multisite & Route 53 subdomains & Hexo Blog & WordP
    1.0前言玩Startup比賽,因需高效快速地做POC原型產品,所以利用AWS云端服務來更快地開發。你會學到:LightSail建立WordpressmultisiteRoute53註冊WordpressSubdomains&GithubCuostomDomainLightSailCustomDomain&SSLHexo快速搭建GihubPages博客+ Route53 Custom......