KDT学习笔记
KDT的定义与构建
- KDT的定义:
KDT类似二叉搜索树,但可以检索
树上每个节点有以下几个域:
左右儿子;当前节点所代表的向量;当前节点划分的维度
- KDT的构建:
类似线段树的建树过程,每次建树的区间减半
众所周知,向量是无法比较大小的,所以每一次划分只能够针对其中一个维度来进行
有两种选择维度的方式:
轮换划分:每一个维度轮着进行
方差划分:优先划分方差较大的维度
因为一般情况下,我们都只是用KDT来维护二维平面,所以用方差划分来实现并不复杂。感性上理解,方差划分应该是要比轮换划分要好一点的,因为方差越大说明这一维的离散程度越大,及时划分肯定更优
因为每一维本身并没有顺序,所以还要找到中位数,用nth_element实现即可
- KDT的查询:
一些复杂度的证明
最重要的一个:超平面经过的区域个数为
其实也类似线段树,一直走到一个完全被询问区间包含的区域就可以直接返回了
为了支持查询,每个节点还要记录其子树内每一维的最大最小值(即维护这个节点管辖的超平面)
- KDT的插入和重构:
KDT有两种写法:一种是线段树的叶子式,一种是二叉搜索树的节点式
上面说的都是线段树的写法,这种写法较为简洁且细节少,但不资瓷插入和删除,或者毒瘤出题人强制在线,这时只能用二叉搜索树
如果不是强制需要二叉搜索树,还是建议用线段树的写法,复杂度比较有保证且常数小
众所周知,二叉搜索树可以被卡到一条链,有两种解决办法:随机打乱插入顺序(但仍然要离线),用替罪羊树重构
就是在插入的时候,如果某个节点
就重构这一棵树,一般情况下
还有一种定期重构的方式,大概就是每
KDT的应用
- 区域查询
CF44G Shooting Gallery
题意:有若干个矩形,每个矩形有一个高度
然后有若干次射击,每次射击会射到包含这个节点且高度最小的矩形,然后这个矩形就会被摧毁
输出每次射击射到的矩形,或指出未射中
不妨先考虑一维的情况:
最直接的想法就是维护用数据结构去维护每一个矩形(这里就是一条线),要支持查询和删除,可以用线段树来维护
但有可能一个矩形(一条线)覆盖的区间是相同的,但是长度不同,所以线段树上每个节点还要开一个set来维护
那么二维情况下,就需要一个四分树(或线段树套线段树)套set,三个log的感人复杂度
那不妨反过来考虑,考虑每个矩形能够匹配哪些射击
很容易想到,把矩形按照高度排序,依次处理每一个矩形。那么只需要查询这个矩形中编号最小的射击点并将其删除即可
具体的,将射击点构造KDT,维护子树内最小没有被删除的节点,每次区间询问完后单点修改即可
- 估价函数和剪枝
[CQOI2016]K 远点对
非常暴力的做法:首先对所有的点建立KDT,然后枚举每个点在KDT中查询更新答案
具体的:建立一棵小根堆维护已知的前
如果枚举的点到
这里的最大距离,只需维护
这里的最大距离和上面的如果
注意最大值和最小值的估价函数是不一样的
最大距离的估价函数和平面的四个端点都算一遍就行了,最小距离的估计函数则要和四条边算一下,并且在平面内则是0
- 标记的维护
既然KDT可以写成线段树的样子,那么就也能像线段树一样维护乱七八糟的标记
上面三道题主要是用来认识KDT的写法以及基本的应用,下面是两道毒瘤题
- KDT优化建图
[NOI2019] 弹跳
一维的情况就是裸的线段树建图,二维的话考虑用KDT来优化建图
注意到这里只有一个点连向区间的操作,所以只需要对入点建立KDT,然后连边从叶子跳到上面连就行了
这样空间复杂度
然后想了一个优化,不知道为什么只能起到负优化的作用:
因为边数比点数大,所以一个点连向某个区间可能不止一条边,只取边权最小的那一条就行了
不过想到的另外一个优化还是可以过的,就是我们要跑dijkstra但不用真的把边建出来,因为每个点到哪些边都是确定的,所以
但这样复杂度其实是不对的,每次从叶子节点的查询需要
更正确的做法,思考dijkstra的松弛过程:找到距离最小的点;把它拿出来更新其他的点;把它删掉
上面三个过程都可以直接用kdt实现,时间复杂度就是严格的
- KDT套其他数据结构
P4848 崂山白花蛇草水
一句话题意:带插入求平面第
套路的,求
因为还有插入,所以考虑在线段树上二分,那么只需要在线段树每个节点都开一个KDT来维护平面就行了
注意重构的写法啊!排序不要排错数组了,也不要写错了