[python刷题模板] 珂朵莉树 ODT(20220928弃用,请看新文)
[python刷题模板] 珂朵莉树 ODT (基于SortedList 20220928弃用,请看新文)
- 一、 算法&数据结构
- 1. 描述
- 2. 复杂度分析
- 3. 常见应用
- 4. 常用优化
- 二、 模板代码
- 1. 区间赋值,区间询问最小值
- 2. 区间赋值,区间查询
- 3. 不存在区间赋值,只有区间加,区间查询
- 4. 不存在区间赋值,只有区间加,区间查询
- 5. 存在区间赋值,整体查询
- 6. 单点赋值,合并区间
- 三、其他
- 四、更多例题
- 五、参考链接
一、 算法&数据结构
1. 描述
- 注意,本篇文章的珂朵莉树实现基于SortedList,assign操作常数较大(2000),因此大数据会TLE。
- 我重新整理了基于跳表的珂朵莉,见新文[python刷题模板] 珂朵莉树 ODT (基于支持随机访问的跳表
- 在网络上搜了很久没怎么找到珂朵莉树的python实现,自己写一个。
- 由于python的有序列表SortedList不是红黑树,所以split要先算begin,再算end。下标代替迭代器,这里和C++实现相反。
- SortedList批量删除使用del tree[begin:end],切片,原理是会用tree[:begin] 连接tree[end:]更新原来的tree。
- 珂朵莉树是建立在数据随机的情况下的一个乱搞算法。
- 当操作中有大量
区间赋值
动作,尤其是大区间
赋值,会把这个区间的值推平
,并且将原本这里边的很多小区间合并
。 - 如果数据随机,可以预见合并区间后,保留下来的区间不会很多。
- 那么查询区间如果不大,只需要处理几个区间即可。
- 底层用有序列表储存,c++set是红黑树,所以珂朵莉树也算树,
- 但python的sortedlist是list套list。。。可能名不副实了。
- 如果有心造数据卡珂朵莉树是可能实现的,数据不保证随机,大范围赋值少,查询多。
- 但这种的可以做特判,因为如果大范围赋值少,那数据范围应该小,没准可以暴力。
2. 复杂度分析
- 在随机数据下,珂朵莉树可达到O(nloglogn)
- split,珂朵莉熟的核心操作,在pos位置切割区间,返回以pos为左端点的区间。O(lgn).
- 之后所有动作都要包含两个split
- 更新assign,O(log2n)
- 查询query, O(lgn)+O©,c是区间内节点数,但c应该小所以速度很客观。
- 所有的query都是暴力操作。
- 找到这个区间所有节点,然后再算。
具体复杂度分析见知乎-珂朵莉树的复杂度分析
3. 常见应用
注意,这类题通常正解是线段树,但珂朵莉在特定情况下吊打正解。
这个特定情况是指:操作中存在大量assign碾平操作。
如果操作中不存在assign,请尽量不要用珂朵莉。(但数据弱的话也没准)
- 区间赋值,区间询问最大最小值。
- 区间赋值,区间询问第K小。
- 区间赋值,区间询问求和
- 区间赋值,区间询问n次方和(一般会有mod)。
这些操作全部暴力处理,因为我们认为:- 在随机数据下,大量区间被合并,询问的区间里不会有太多节点。
4. 常用优化
实现时我做了数次优化,提升不大记一下。
- 这里要用SortedKeyList,因为只能比较左端点。
- 从元组变成结构体,这样就可以直接修改。而且在结构体里实现小于运算,则改用SortedList。
- split时,本应删除原节点,加入两个节点。但实际上插入的左节点和原节点只差了一个右边界,因此可以直接修改。
- split生成时调用节点的解包函数,好写还快一点。
二、 模板代码
1. 区间赋值,区间询问最小值
例题: 715. Range 模块
这题当然可以线段树,套一下板就行。
珂朵莉树实测比线段树快3倍。
当然最佳方案是有序列表合并拆分线段。
class ODTNode:__slots__ = ['l','r','v']def __init__(self,l,r,v):self.l,self.r,self.v = l,r,vdef __lt__(self,other):return self.l<other.ldef jiebao(self):return self.l,self.r,self.v
class ODT:def __init__(self,l,r,v):from sortedcontainers import SortedListself.tree = SortedList([ODTNode(l,r,v)])def split(self,pos):""" 在pos位置切分,返回左边界l为pos的线段下标"""tree = self.treep = tree.bisect_left(ODTNode(pos,0,0))if p != len(tree) and tree[p].l == pos:return p p -= 1l,r,v = tree[p].jiebao()tree[p].r = pos-1# tree.pop(p)# tree.add(ODTNode(l,pos-1,v))tree.add(ODTNode(pos,r,v))return p+1def assign(self,l,r,v):"""把[l,r]区域全变成val"""tree = self.treebegin = self.split(l)end = self.split(r+1)del tree[begin:end]# for i in range(begin,end):# tree.pop(begin)tree.add(ODTNode(l,r,v))# 以下操作全是暴力,寄希望于这里边元素不多。def add_interval(self,l,r,val):"""区间挨个加"""tree = self.treebegin = self.split(l)end = self.split(r+1)for i in range(begin,end):tree[i].v += valdef query_min(self,l,r):"""查找x,y区间的最小值""" begin = self.split(l)end = self.split(r+1)return min(node.v for node in self.tree[begin:end])def query_kth(self,l,r,k):"""查找[x,y]区间第k小的数"""begin = self.split(l)end = self.split(r+1)vs = [(node.v,node.r-node.l+1) for node in self.tree[begin:end]] # v和v的个数,排序for v,d in sorted(vs): # 挨个往外丢,缩小kk -= d if k <= 0:return v def query_sum_of_pow(self,l,r,x):"""求区间x次方的和,一般还得mod,那就要手写快速幂"""s = 0begin = self.split(l)end = self.split(r+1)for node in self.tree[begin:end]:s += (node.v**x) * (node.r-node.l+1)return sclass RangeModule:def __init__(self):self.tree = ODT(1,10**9,0)def addRange(self, left: int, right: int) -> None:self.tree.assign(left,right-1,1)def queryRange(self, left: int, right: int) -> bool:return 1 == self.tree.query_min(left,right-1)def removeRange(self, left: int, right: int) -> None:self.tree.assign(left,right-1,0)
2. 区间赋值,区间查询
链接: 729. 我的日程安排表 I
book是给区间全赋值1,区间操作前检查是否这个区间有非0的值,sum或者max都可以。
线段树
也可以,珂朵莉树
快一点
class ODTNode:__slots__ = ['l','r','v']def __init__(self,l,r,v):self.l,self.r,self.v = l,r,vdef __lt__(self,other):return self.l<other.ldef jiebao(self):return self.l,self.r,self.v
class ODT:def __init__(self,l,r,v):from sortedcontainers import SortedListself.tree = SortedList([ODTNode(l,r,v)])def split(self,pos):""" 在pos位置切分,返回左边界l为pos的线段下标"""tree = self.treep = tree.bisect_left(ODTNode(pos,0,0))if p != len(tree) and tree[p].l == pos:return p p -= 1l,r,v = tree[p].jiebao()tree[p].r = pos-1# tree.pop(p)# tree.add(ODTNode(l,pos-1,v))tree.add(ODTNode(pos,r,v))return p+1def assign(self,l,r,v):"""把[l,r]区域全变成val"""tree = self.treebegin = self.split(l)end = self.split(r+1)del tree[begin:end]tree.add(ODTNode(l,r,v))# 以下操作全是暴力,寄希望于这里边元素不多。def query_max(self,l,r):"""查找x,y区间的最小值""" begin = self.split(l)end = self.split(r+1)return max(node.v for node in self.tree[begin:end])class MyCalendar:def __init__(self):self.odt = ODT(0,10**9,0)def book(self, start: int, end: int) -> bool:if self.odt.query_max(start,end-1) == 1:return Falseself.odt.assign(start,end-1,1)return True
3. 不存在区间赋值,只有区间加,区间查询
链接: 731. 我的日程安排表 II
这题没有assign本不该用珂朵莉做,但这个数据比较弱,确实能过,而且吊打线段树。
由于是求区间内一个超过1的数,因此可以区间查询时,提前退出。
class ODTNode:__slots__ = ['l','r','v']def __init__(self,l,r,v):self.l,self.r,self.v = l,r,vdef __lt__(self,other):return self.l<other.ldef jiebao(self):return self.l,self.r,self.v
class ODT:def __init__(self,l,r,v):from sortedcontainers import SortedListself.tree = SortedList([ODTNode(l,r,v)])def split(self,pos):""" 在pos位置切分,返回左边界l为pos的线段下标"""tree = self.treep = tree.bisect_left(ODTNode(pos,0,0))if p != len(tree) and tree[p].l == pos:return p p -= 1l,r,v = tree[p].jiebao()tree[p].r = pos-1# tree.pop(p)# tree.add(ODTNode(l,pos-1,v))tree.add(ODTNode(pos,r,v))return p+1def assign(self,l,r,v):"""把[l,r]区域全变成val"""tree = self.treebegin = self.split(l)end = self.split(r+1)del tree[begin:end]tree.add(ODTNode(l,r,v))# 以下操作全是暴力,寄希望于这里边元素不多。def add_interval(self,l,r,val):"""区间挨个加"""tree = self.treebegin = self.split(l)end = self.split(r+1)for i in range(begin,end):tree[i].v += valdef query_max(self,l,r):"""查找x,y区间的最小值""" begin = self.split(l)end = self.split(r+1)return max(node.v for node in self.tree[begin:end])def query_has_greater_than(self,l,r,val):"""查找x,y区间是否有大于val的数""" begin = self.split(l)end = self.split(r+1)return any(node.v>val for node in self.tree[begin:end])class MyCalendarTwo:def __init__(self):self.odt = ODT(0,10**9,0)def book(self, start: int, end: int) -> bool:if self.odt.query_has_greater_than(start,end-1,1) :return Falseself.odt.add_interval(start,end-1,1)return True
4. 不存在区间赋值,只有区间加,区间查询
链接: 732. 我的日程安排表 III
同上题,没有assign,且每次区间询问都是整体询问,我本以为不行的,但是试了一下,数据更友好~
这里也可以优化,每次最大值更新都在插入后询问,因此最大值可以储存下来,每次和更新后的区间比较更新即可。
class ODTNode:__slots__ = ['l','r','v']def __init__(self,l,r,v):self.l,self.r,self.v = l,r,vdef __lt__(self,other):return self.l<other.ldef jiebao(self):return self.l,self.r,self.v
class ODT:def __init__(self,l,r,v):from sortedcontainers import SortedListself.tree = SortedList([ODTNode(l,r,v)])def split(self,pos):""" 在pos位置切分,返回左边界l为pos的线段下标"""tree = self.treep = tree.bisect_left(ODTNode(pos,0,0))if p != len(tree) and tree[p].l == pos:return p p -= 1l,r,v = tree[p].jiebao()tree[p].r = pos-1# tree.pop(p)# tree.add(ODTNode(l,pos-1,v))tree.add(ODTNode(pos,r,v))return p+1def assign(self,l,r,v):"""把[l,r]区域全变成val"""tree = self.treebegin = self.split(l)end = self.split(r+1)del tree[begin:end]tree.add(ODTNode(l,r,v))# 以下操作全是暴力,寄希望于这里边元素不多。def add_interval(self,l,r,val):"""区间挨个加"""tree = self.treebegin = self.split(l)end = self.split(r+1)m = 0for i in range(begin,end):tree[i].v += valm = max(m,tree[i].v)return mdef query_max(self,l,r):"""查找x,y区间的最大值""" begin = self.split(l)end = self.split(r+1)return max(node.v for node in self.tree[begin:end])def query_all_max(self,):"""查找x,y区间的最大值""" begin = self.split(0)end = self.split(10**9+1)return max(node.v for node in self.tree[begin:end])
class MyCalendarThree:def __init__(self):self.odt = ODT(0,10**9,0)self.m = 0def book(self, start: int, end: int) -> int: self.m = max(self.m,self.odt.add_interval(start,end-1,1))return self.m# return self.odt.query_all_max()
5. 存在区间赋值,整体查询
链接: 699. 掉落的方块
优化维护最大值思路类似上题。
但是存在赋值,因此可以珂朵莉,实测效率和线段树差不多。
class ODTNode:__slots__ = ['l','r','v']def __init__(self,l,r,v):self.l,self.r,self.v = l,r,vdef __lt__(self,other):return self.l<other.ldef jiebao(self):return self.l,self.r,self.v
class ODT:def __init__(self,l,r,v):from sortedcontainers import SortedListself.tree = SortedList([ODTNode(l,r,v)])def split(self,pos):""" 在pos位置切分,返回左边界l为pos的线段下标"""tree = self.treep = tree.bisect_left(ODTNode(pos,0,0))if p != len(tree) and tree[p].l == pos:return p p -= 1l,r,v = tree[p].jiebao()tree[p].r = pos-1# tree.pop(p)# tree.add(ODTNode(l,pos-1,v))tree.add(ODTNode(pos,r,v))return p+1def assign(self,l,r,v):"""把[l,r]区域全变成val"""tree = self.treebegin = self.split(l)end = self.split(r+1)del tree[begin:end]tree.add(ODTNode(l,r,v))# 以下操作全是暴力,寄希望于这里边元素不多。def add_interval(self,l,r,val):"""区间挨个加"""tree = self.treebegin = self.split(l)end = self.split(r+1)for i in range(begin,end):tree[i].v += valdef query_max(self,l,r):"""查找x,y区间的最大值""" begin = self.split(l)end = self.split(r+1)return max(node.v for node in self.tree[begin:end])def query_all_max(self,):"""查找x,y区间的最大值""" begin = self.split(0)end = self.split(10**9+1)return max(node.v for node in self.tree[begin:end])
class Solution:def fallingSquares(self, positions: List[List[int]]) -> List[int]:odt = ODT(1,10**9,0) ans = []m = 0for l,d in positions:h = odt.query_max(l,l+d-1)odt.assign(l,l+d-1,h+d)m = max(m,h+d)ans.append(m)return ans
6. 单点赋值,合并区间
链接: 352. 将数据流变为多个不相交区间
实际上是线段合并。
class ODTNode:def __init__(self,l,r,v):self.l,self.r,self.v = l,r,vdef __lt__(self,other):return self.l<other.ldef jiebao(self):return self.l,self.r,self.v# def __str__(self):# return str((self.l,self.r,self.v))# def __repr__(self):# return str((self.l,self.r,self.v))
class ODT:def __init__(self,l,r,v):from sortedcontainers import SortedListself.tree = SortedList([ODTNode(l,r,v)]) def split(self,pos):""" 在pos位置切分,返回左边界l为pos的线段下标"""tree = self.treep = tree.bisect_left(ODTNode(pos,0,0))if p != len(tree) and tree[p].l == pos:return p p -= 1l,r,v = tree[p].jiebao()tree[p].r = pos-1# tree.pop(p)# tree.add(ODTNode(l,pos-1,v))tree.add(ODTNode(pos,r,v))return p+1def assign(self,l,r,v):"""把[l,r]区域全变成val"""tree = self.treebegin = self.split(l)end = self.split(r+1)del tree[begin:end]tree.add(ODTNode(l,r,v))# 以下操作全是暴力,寄希望于这里边元素不多。def query_all_intervals(self):tree = self.treelines = []l = r = -1for node in tree :if node.v == 0:if l != -1:lines.append([l,r]) l = -1else:if l == -1:l = node.l r = node.r# for line in lines:# self.assign(line[0],line[1],1)# print(self.tree)return linesclass SummaryRanges:def __init__(self):self.odt = ODT(0,10**4+1,0)def addNum(self, val: int) -> None:self.odt.assign(val,val,1)def getIntervals(self) -> List[List[int]]:return self.odt.query_all_intervals()
三、其他
- 待补充
四、更多例题
- 待补充
五、参考链接
- 链接: 知乎-算法学习笔记(15): 珂朵莉树
[python刷题模板] 珂朵莉树 ODT(20220928弃用,请看新文)相关推荐
- [python刷题模板] 珂朵莉树 ODT (基于支持随机访问的跳表
[python刷题模板] 珂朵莉树 ODT (基于支持随机访问的跳表) 一. 算法&数据结构 1. 描述 2. 复杂度分析 3. 常见应用 4. 常用优化 二. 模板代码 0. 区间推平(lg ...
- 珂朵莉树/ODT 学习笔记
珂朵莉树/ODT 学习笔记 起源自 CF896C.珂朵莉yyds! 核心思想 把值相同的区间合并成一个结点保存在 set 里面. 用处 骗分.只要是有区间赋值操作的数据结构题都可以用来骗分.在数据随机 ...
- [转]我的数据结构不可能这么可爱!——珂朵莉树(ODT)详解
参考资料: Chtholly Tree (珂朵莉树) (应某毒瘤要求,删除链接,需要者自行去Bilibili搜索) 毒瘤数据结构之珂朵莉树 在全是珂学家的珂谷,你却不知道珂朵莉树?来跟诗乃一起学习珂朵 ...
- 浅谈珂朵莉树(ODT)
前言 珂学家狂喜( 文章目录 前言 一.珂朵莉树来源 二.珂朵莉树 1.珂朵莉树有什么用? 2.原理是什么? a.存储 b.分割结点 c.推平 d.剩余操作 3.复杂度分析 三.珂朵莉树例题 1.P4 ...
- 珂朵莉树(odt老司机树)
传送门 题意:对于一段区间一共有四种操作: 题解:珂朵莉树板题 珂朵莉树,又称Old Driver Tree(ODT).是一种基于std::set的暴力数据结构. 关键操作:推平一段区间,使一整段区间 ...
- Chtholly Tree (珂朵莉树) ODT
ODT,OldDriverTree,又名ChthollyTree O D T , O l d D r i v e r T r e e , 又 名 C h t h o l l y T r e e ODT ...
- 珂朵莉树(永远喜欢珂朵莉/doge)
目录 前言 可能用到前置知识 背景 构建珂朵莉树 核心函数 珂朵莉树在实际题目使用 对珂朵莉树的一些感想 最后的最后 前言 最近刚刚学内容大概是借鉴的吧,感觉这个数据结构不仅简单,还很强,有着非常柯学 ...
- 【luogu CF896C】Willem, Chtholly and Seniorious(珂朵莉树)
Willem, Chtholly and Seniorious 题目链接:luogu CF896C 题目大意 要你维护一个数组,会有区间加值,区间赋值,求区间第 x 小,求区间每个数的 x 次方和模 ...
- CF896C Willem, Chtholly and Seniorious(珂朵莉树)
中文题面 珂朵莉树的板子--这篇文章很不错 据说还有奈芙莲树和瑟尼欧里斯树-- 等联赛考完去学一下(逃 1 //minamoto 2 #include<bits/stdc++.h> 3 # ...
最新文章
- DIV限制宽度,字符断行,避免变形
- Linux安装最新Redis
- Linux的Nginx三:类型|特点
- pSort CodeForces - 28B(并查集)
- 《程序员代码面试指南》第二章 链表问题 反转部分单向链表
- MySQL的前缀索引及Oracle的类似实现
- 51 nod 1006 最长公共子序列Lcs
- 【论文解读】EMNLP2019 如何在Transformer中融入句法树信息?这里给出了一种解决方案...
- 传智播客 刘意_2015年Java基础视频-深入浅出精华版 笔记(day01~day10)(2015年11月17日20:47:22)...
- hrbust 哈理工oj 1921 三原色(改进版)【集合相关问题】
- ISO 18000-6c 访问标签--应用程序访问操作ISO 18000-6C标签的方法
- 制作一款游戏这么简单
- python研究背景与意义_课题研究的背景和意义
- SPSS实现线性回归
- mmo服务器 性能测试,【Zinx应用-MMO游戏案例-(5)构建项目及用户上线】Golang轻量级并发服务器框架...
- vue中路由跳转怎样刷新页面保证页面更新
- 牛客练习赛46-华华跟奕奕玩游戏(期望+逆元)
- linux 下安装xampp
- NCCL源码解析②:Bootstrap网络连接的建立
- 硬盘分类(HDD、HHD、SSD)简介