[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. 复杂度分析

  1. 在随机数据下,珂朵莉树可达到O(nloglogn)
  2. split,珂朵莉熟的核心操作,在pos位置切割区间,返回以pos为左端点的区间。O(lgn).
  3. 之后所有动作都要包含两个split
  4. 更新assign,O(log2n)
  5. 查询query, O(lgn)+O©,c是区间内节点数,但c应该小所以速度很客观。
    • 所有的query都是暴力操作。
    • 找到这个区间所有节点,然后再算。
      具体复杂度分析见知乎-珂朵莉树的复杂度分析

3. 常见应用

注意,这类题通常正解是线段树,但珂朵莉在特定情况下吊打正解。
这个特定情况是指:操作中存在大量assign碾平操作。
如果操作中不存在assign,请尽量不要用珂朵莉。(但数据弱的话也没准)
  1. 区间赋值,区间询问最大最小值。
  2. 区间赋值,区间询问第K小。
  3. 区间赋值,区间询问求和
  4. 区间赋值,区间询问n次方和(一般会有mod)。
    这些操作全部暴力处理,因为我们认为:

    • 在随机数据下,大量区间被合并,询问的区间里不会有太多节点。

4. 常用优化

 实现时我做了数次优化,提升不大记一下。
  1. 这里要用SortedKeyList,因为只能比较左端点。
  2. 从元组变成结构体,这样就可以直接修改。而且在结构体里实现小于运算,则改用SortedList。
  3. split时,本应删除原节点,加入两个节点。但实际上插入的左节点和原节点只差了一个右边界,因此可以直接修改。
  4. 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()

三、其他

  1. 待补充

四、更多例题

  • 待补充

五、参考链接

  • 链接: 知乎-算法学习笔记(15): 珂朵莉树

[python刷题模板] 珂朵莉树 ODT(20220928弃用,请看新文)相关推荐

  1. [python刷题模板] 珂朵莉树 ODT (基于支持随机访问的跳表

    [python刷题模板] 珂朵莉树 ODT (基于支持随机访问的跳表) 一. 算法&数据结构 1. 描述 2. 复杂度分析 3. 常见应用 4. 常用优化 二. 模板代码 0. 区间推平(lg ...

  2. 珂朵莉树/ODT 学习笔记

    珂朵莉树/ODT 学习笔记 起源自 CF896C.珂朵莉yyds! 核心思想 把值相同的区间合并成一个结点保存在 set 里面. 用处 骗分.只要是有区间赋值操作的数据结构题都可以用来骗分.在数据随机 ...

  3. [转]我的数据结构不可能这么可爱!——珂朵莉树(ODT)详解

    参考资料: Chtholly Tree (珂朵莉树) (应某毒瘤要求,删除链接,需要者自行去Bilibili搜索) 毒瘤数据结构之珂朵莉树 在全是珂学家的珂谷,你却不知道珂朵莉树?来跟诗乃一起学习珂朵 ...

  4. 浅谈珂朵莉树(ODT)

    前言 珂学家狂喜( 文章目录 前言 一.珂朵莉树来源 二.珂朵莉树 1.珂朵莉树有什么用? 2.原理是什么? a.存储 b.分割结点 c.推平 d.剩余操作 3.复杂度分析 三.珂朵莉树例题 1.P4 ...

  5. 珂朵莉树(odt老司机树)

    传送门 题意:对于一段区间一共有四种操作: 题解:珂朵莉树板题 珂朵莉树,又称Old Driver Tree(ODT).是一种基于std::set的暴力数据结构. 关键操作:推平一段区间,使一整段区间 ...

  6. 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 ...

  7. 珂朵莉树(永远喜欢珂朵莉/doge)

    目录 前言 可能用到前置知识 背景 构建珂朵莉树 核心函数 珂朵莉树在实际题目使用 对珂朵莉树的一些感想 最后的最后 前言 最近刚刚学内容大概是借鉴的吧,感觉这个数据结构不仅简单,还很强,有着非常柯学 ...

  8. 【luogu CF896C】Willem, Chtholly and Seniorious(珂朵莉树)

    Willem, Chtholly and Seniorious 题目链接:luogu CF896C 题目大意 要你维护一个数组,会有区间加值,区间赋值,求区间第 x 小,求区间每个数的 x 次方和模 ...

  9. CF896C Willem, Chtholly and Seniorious(珂朵莉树)

    中文题面 珂朵莉树的板子--这篇文章很不错 据说还有奈芙莲树和瑟尼欧里斯树-- 等联赛考完去学一下(逃 1 //minamoto 2 #include<bits/stdc++.h> 3 # ...

最新文章

  1. DIV限制宽度,字符断行,避免变形
  2. Linux安装最新Redis
  3. Linux的Nginx三:类型|特点
  4. pSort CodeForces - 28B(并查集)
  5. 《程序员代码面试指南》第二章 链表问题 反转部分单向链表
  6. MySQL的前缀索引及Oracle的类似实现
  7. 51 nod 1006 最长公共子序列Lcs
  8. 【论文解读】EMNLP2019 如何在Transformer中融入句法树信息?这里给出了一种解决方案...
  9. 传智播客 刘意_2015年Java基础视频-深入浅出精华版 笔记(day01~day10)(2015年11月17日20:47:22)...
  10. hrbust 哈理工oj 1921 三原色(改进版)【集合相关问题】
  11. ISO 18000-6c 访问标签--应用程序访问操作ISO 18000-6C标签的方法
  12. 制作一款游戏这么简单
  13. python研究背景与意义_课题研究的背景和意义
  14. SPSS实现线性回归
  15. mmo服务器 性能测试,【Zinx应用-MMO游戏案例-(5)构建项目及用户上线】Golang轻量级并发服务器框架...
  16. vue中路由跳转怎样刷新页面保证页面更新
  17. 牛客练习赛46-华华跟奕奕玩游戏(期望+逆元)
  18. linux 下安装xampp
  19. NCCL源码解析②:Bootstrap网络连接的建立
  20. 硬盘分类(HDD、HHD、SSD)简介

热门文章

  1. 2007年中考化学模拟试题一
  2. uni-app h5修改浏览器导航栏的 title以及icon
  3. BUAA五子棋危险判断
  4. 生成Window的远程桌面连接rdp文件, 及rdp中代码配置说明
  5. WebStorm添加px转rem单位插件
  6. linux简述什么是shell,【Linux小知识】什么是shell?
  7. T-SQL 存储过程
  8. [BZOJ3786]星系探索
  9. matlab 保存为矢量图,将绘图保存为图像或向量图形文件
  10. Pytorch 保存和加载模型