珂朵莉树
珂朵莉
我永远喜欢珂朵莉。
如果幸福有颜色,那一定是终末之红染尽的蓝色!
萌娘百科:
珂朵莉树
珂朵莉树是基于 set
的暴 (pian) 力 (fen) 算法。
前置知识
优点
珂朵莉全身都是优点。
码量小,思路清晰易查错。
应用范围
-
含推平操作,即将一个区间的数全部更新为相同的数。
-
数据随机(防止毒瘤出题人卡珂朵莉)。
基本思想
用 set
储存三元组 ,将部分连续相同的元素储存到一起。
核心
split
是整个 ODT
的核心操作。
因为三元组存的区间和每次操作维护的区间不一定完全相同,所以操作之前我们要将区间先分裂,然后再维护。
直观感受
假设现在的珂树长这样:
接下来要将区间 推平为 。
-
将区间 <4,5,7> 和 <6,14,2> 分裂成 <4,4,7>,<5,5,7>,<6,10,2>,<11,14,2> 。
-
将 <5,5,7> 和 <6,10,2> 删掉。
-
将 <5,10,-3> 加入。
此时的珂树长这样:
这就是下面的分裂 (split) 和推平 (assign) 操作。
实现
基本概念与储存
只需要存 set
需要的三元组,然后用重载运算符,让 set
按照区间的左端点排序。
struct C_Tree{ int le,ri; mutable int val; C_Tree(int le,int ri=0,int val=0): le(le),ri(ri),val(val){} bool operator <(const C_Tree &b)const { return le<b.le; } }; set<C_Tree>T;
分裂
将区间 分裂成 和 。
显然,让 now
为区间左端点的时候并不需要分裂。
否则需要分裂。删除原区间后将两端插入即可。
#define IT set<C_Tree>::iterator IT split(int now) { IT i=T.lower_bound(C_Tree(now)); if(i!=T.end()&&i->le==now)return i; i--;int l=i->le,r=i->ri,v=i->val; T.erase(i); T.insert(C_Tree(l,now-1,v)); return T.insert(C_Tree(now,r,v)).first; }
推平
如果只分裂,那么 set
的大小就会增大直到达到 n ,此时我们再用 set
就会很慢。所以需要将一个区间推平。
先把要推平的区间分裂出来,然后一并删除,将新区间插入。
void assign(int l,int r,int k) { IT ir=split(r+1),il=split(l); tre.erase(il,ir); tre.insert(C_tree(l,r,k)); }
暴力
用 set
维护好三元组之后,剩余的操作就基于 set
进行暴力维护就好了。
比如这个非官方模板。
- 区间加
void update(int l,int r,int k) { IT ir=split(r+1),il=split(l); while(il!=ir) { il->val+=k; il++; } }
- 区间查值
typedef pair<int,int>Pr; int ask_rank(int l,int r,int k) { vector<Pr>ans_; IT ir=split(r+1),il=split(l); for(IT i=il;i!=ir;i++) ans_.push_back((Pr){i->val,((i->ri)-(i->le)+1)}); sort(ans_.begin(),ans_.end()); vector<Pr>::iterator i=ans_.begin(); while(i!=ans_.end()) { k-=i->second; if(k<=0)return i->first; i++; } return -1; }
- 区间次幂和
int ksm(int a,int b,int p) { int ans=1;a%=p; while(b) { if(b&1)ans=(ans*a)%p; a=(a*a)%p; b>>=1; } return ans; } int ask_sum(int l,int r,int x,int y) { int ans=0; IT ir=split(r+1),il=split(l); for(IT i=il;i!=ir;i++) ans=(ans+ksm(i->val,x,y)*(i->ri-i->le+1))%y; return ans; }
关于对适用条件的解释
-
含推平
没有推平操作会让
set
的大小趋近甚至达到 的级别,那么用珂朵莉树就会 T 到飞起。 -
数据随机
这样可以有较大概率将某段区间合并,从而将
set
的大小急剧下降,使其大小稳定在 的范围左右,而不像某些 毒瘤题目 (@ 序列操作 ) 将珂朵莉树卡的死死的。像这样:
话说,前三个点跑的还挺快。所以,随机数据下的珂朵莉树的时间复杂度就是 的。
例题
部分含树剖
以下则是看似能用珂朵莉树实则都会被卡的题
优化
对于某些不保证随机数据的题目,如果一时想不到正解,想用珂朵莉树骗分,但是又怕毒瘤出题人将珂朵莉树卡的连渣都不剩,那么可以试着对珂朵莉树加一些小优化。
不过说实话,网上对于优化这种东西讲的真不多,可能是感觉不优化用来骗分已经足够了。
启发式推平
这名字是我胡扯的……
其实就是在每次 assign 的时候,比较一下其与两侧的块是否相同,如果相同则一起合并。
这种优化在大多数题中应该是没有什么作用的,但在权值比较少的题中则表现的很优秀。
比如这个题,权值只有 。
assign
只需要这样写:
void assign(int l,int r,int k) {//此代码写于 2022.10.13 IT ir=split(r+1),il=split(l); IT pre=il;pre--; if(pre->val==k)il=pre,l=il->le; if(ir->val==k)r=ir->ri,ir++; T.erase(il,ir); T.insert(C_Tree(l,r,k)); }
由于迭代器的特性,加加减减的东西写出来都特别丑……
还有这个题,权值只有 。
我是这样写的:
void assign(int l,int r,char ch) {//此代码写于 2022.04.29 IT ir=split(r+1),il=split(l); il--; if(ch!=il->val)il++; if(ch==ir->val)ir++; l=il->le;r=ir->ri; T.erase(il,ir); T.insert(C_Tree(l,r,ch)); }
感觉好像之前写的比较好看点……
定期重构
说实话,这种东西我就见过一次。
热知识,每个字上都可以放一个链接。
但感觉好像有点像块状链表?
等我学会了就来补文章。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具