K-D Tree
K-D Tree (K - Dimension Tree),是一种用来解决 \(k\) 维空间信息的数据结构;一般在 OI 竞赛中,以 \(k=2\) 为多
K-D Tree 实际上是一个二叉搜索树;而为了让复杂度尽量优(注意,K-D Tree 在某些问题可以被卡成暴力的复杂度),我们会让树高尽可能的小,因此它也属于“平衡树”的一种
建树
-
我们先按照某些方法,从 \(k\) 维选出某一维 \(i\);
-
然后将点按照第 \(i\) 维排序,选出该维度上的中位数的点 \(u\),将 \(u\) 作为当前子树的根;
-
对于 \(\le u_i\) (\(u\) 在第 \(i\) 维的坐标)的点,分到左子树;\(> u_i\) 的点,分到右子树;
-
重复步骤 \(1\)
对于步骤 \(1\) 维度的选取方法,我们有以下可选项:
-
\(rand()\bmod k\)
-
假设上一次选取的是 \(p\),那么这次选 \((p+1)\bmod k\)
-
选择方差最大的维度
一般来说,第三种方法是最优的方案,证明可以见《kdtree划分空间维度选择使用“最大方差法”的好处》
再来看步骤 \(3\),如果我们直接排序,复杂度将会多带一个 \(\log n\),而其实我们只关心分到左右哪边,而不需要确切的顺序,因此可以使用 nth_element
建好树了,那这个东西到底有什么用呢?通过几道例题来了解一下:
P4357 [CQOI2016]K 远点对
先直接建树,同时,对于每个子树的根节点,我们都要记录子树内最大的 \(x,y\) 值和最小的 \(x,y\) 值
然后我们考虑每个点都跑一次 query
,具体过程如下:
假设我们现在到达了结点 \(u\),计算 \(u\) 到枚举的点的距离,更新答案
然后,我们计算左子树到枚举的点的最远可能距离 \(Ld\),以及右子树到枚举的点的最远可能距离 \(Rd\)
我们将 \(Ld\) 与目前的第 \(k\) 远进行比较,如果 \(Ld>\) 第 \(k\) 远,就递归到左子树;右子树同理
然后有一个小优化,就是如果 \(Ld>Rd\),我们优先递归左子树;反之优先递归右子树
P4148 简单题
这道题要求强制在线,且内存只有 \(20\text{MB}\),杀掉许多的 cdq 分治、树套树
但时限有足足 8s,给够了 K-D Tree 时间
先不管加权,先来做查询:我们每个结点还都记录子树内最大的 \(x,y\) 和 最小的 \(x,y\),这四个值恰好组成一个最大可能矩形
当询问一个矩形 \(M\) 时,我们与每个结点 \(u\) 比较:如果 \(M\) 包含了 \(u\) 的最大可能矩形,那么我们将这整个子树的权值和都计入答案,并不用再递归;如果 \(M\) 与 \(u\) 的最大可能矩形相离,那么直接返回;如果是有相交,那么我们就判断 \(u\) 是否计入答案,然后递归左右子树
再来看加权操作,其实我们可以看作每次都新增一个结点
我们按照二叉搜索树的方法将点加入
但这可能导致树高不正确,使得被卡回暴力
因此,我们在这里引入替罪羊树暴力重建的思想:对于一个子树 \(u\),如果 \(\text{sz}_u * \alpha\) 小于左或右子树的大小,那么我们就将子树 \(u\) 重建。一般而言,\(\alpha \in [0.6,~0.7]\)
练习题
P4390 [BOI2007]Mokia 摩基亚
(与上一题一模一样,但没有了强制在线,要不用 cdq 试一试?)
P2479 [SDOI2010]捉迷藏
(求最近和最远点对,曼哈顿距离)
P6224 [BJWC2014]数据
(与上一题类似,不过带有插入新节点)
进阶
四维偏序问题,考虑将一维进行排序
朴素的 DP 转移方程:
我们每次转移就从 K-D Tree 上找最大值,更新 \(f_i\) 后就把点 \(i\) 加到 K-D Tree 中
寻找最优答案时记得剪枝
P5621 [DBOI2019]德丽莎世界第一可爱
(上一题的加权版本,没意思)
一样套路记录子树中最大最小的 \(x,y\)
然后判断矩形的四个点是否都符合或都不符合条件,进行剪枝
考虑将第一维从小到大排序,第二维看成权值,三、四维用来建树
每次判断一个点 \(u\) 是否贡献,就是在 K-D Tree 上找到三、四维都大于点 \(u\),而权值小于点 \(u\) 的结点
查询完后再将点 \(u\) 加入到 K-D Tree 中,复杂度就优化到 \(O(n\sqrt n)\)
(不知道为什么,如果按照第四维排序,第三维看成权值,这样会 TLE,看来 K-D Tree 真的能被卡死)
P2093 [国家集训队]JZPFAR
(做法类似 K 远点对)