KDtree学习笔记

参照lyc的ppt

先从二维入手.KDtree一种可以高效处理K维空间信息的数据结构。

它有类似二叉搜索树的结构,我们每个节点都代表一个点,它的子树代表空间内的一块

build

建树的思想很好理解,比如我们要对一个K维的东西建kdtree,那么我们为了保证复杂度,我们要做到

  1. 轮流(均衡)选择维度,以保证在任意连续k层里每个维度都被切割到。
  2. 每次在维度上选择切割点时选择该维度上的“中位点”,保证每次分成的两棵子树大小尽量相等。

第二个好理解,对于第一个,我们这么想,如果不均衡,那么切出来的空间就很长条状,可能不利于之后查询的复杂度。

层高显然是log

int K=2;//维数
struct point { int x[2], v; } P[_];
struct KDT { int w, s, c[2], vmin[2], vmax[2]; point x; } t[_];
#define lc t[p].c[0]
#define rc t[p].c[1]
bool compare(const int i, const int j) {return t[i].x[k] < t[j].x[k];}
// update vmin, vmax
int build(const int l, const int r, const int k)
{
    if (l > r) return 0;
    ci m(l + r >> 1), p(newnode());
    std::nth_element(P + l, P + m, P + r + 1, compare);
    t[p].x = P[m];
    lc = build(l, m - 1, (k + 1) % K);
    rc = build(m + 1, r, (k + 1) % K);
    update(p); return p;
}

lyc说kdtree能实现线段树的所有操作。确实,很像,都是非常非常平衡的一种树。

query

对于一个空间的查询,我们肯定要查询一些点,就像线段树一样。我们查询的点分为两类。一类是完全包含于查询空间,一类是和查询区间有交。第二类是任选一条线,会穿过多少矩形。

我们定义孙节点为子节点的子节点,那么最多会有两个孙节点和这条直线相交

\(T(n)=2T(\frac{n}{4})+O(1)\)

拓展到K维,我们对于一个空间,割K刀,然后我们要找一个二维的东西使得其割掉的东西最多,所以一定有一个维度下的一块没有被割掉,那么就证完了

\(T(n)=2^{k-1}T(\frac{n}{2^k})+O(1)\)

\(T(n)=n^{1-\frac{1}{k}}\)

当然,最后的复杂度还要乘上有几条边/几个面,所以这个复杂度挺逆天的,大多数的时候都是K=2的时候在做。

所以我们从最大的往里面找,如果当前的交为空那么就是0,否则如果包含了,那么直接返回

insert

我们二进制分组每个点都被插入\(log\)次,时间复杂度\(O(n\log^{2}n)\),build有一个\(log\)。具体怎么写?就是吧所有点找出来,然后搞,可以看OIwiki,里面挺优美的。

邻域查询

也就是平面最近点对。枚举每个节点,然后我们直接剪枝。我们维护每个矩形的范围,如果已经大于答案了,那么就不递归下去了那么

P4148

板子。

P4357

我们搞一个堆。然后每个点都找前\(K\)远,然后每次和堆顶搞就好了。
重载运算符要写在结构体下面呜
tmKDtree过不了是吧,这我干嘛写这个啊

P2479

啊,这怎么做啊
这个比较搞的一点就是我们很难剪枝。但这题明显就可以剪很多枝。我们可不可以直接
啊?有点被束缚思路了
首先我们要求每个点的最近距离和最远距离,哦,这是曼哈顿,我们喜欢你!
那就说的通了。我们按照\(x\)为第一关键字,\(y\)为第二关键字排序,然后每个点往前和往后找最小和最大。往前往后可以做两遍,所以我们转化成了,然后\(x\)的相对大小已经知道了,对于\(y\)分讨。
拆式子
对于左边的
\(x-x_{i}+y-y_{i}\),那么我们就是要找\(x+y\)最大/最小的
对于右边的
\(x-x_{i}+y_{i}-y\),那么我们就是要找\(x-y\)最大/最小的
这个显然可以使用线段树维护
然后就做完了
曼哈顿距离还是很有用的,因为没有欧几里得距离那坨东西

考虑KDtree怎么做,没意义了吧,只是实现上面的功能而已

P4169

一眼CDQ
到时候做吧

P2093

这个直接KDtree就行了,板子。
一开始被卡了,然后加个剪枝就过了
就是优先遍历估价函数优的子树,这样会快很多啊

P4390

双倍经验,没话说

P4475

首先把式子列出来吧,然后就是类似一个三角形的求和,这怎么做啊。离线试试?
向量学傻了,容易发现这是两个向量的点乘。
开玩笑的。
我们直接把这个三角形放到KDtree里查询,KDtree的复杂度证起来很简单,就是把网格图画出来然后发现和矩形经过的网格数量差不多。然后就做完了?你说的对,但是这个题目有正负的,分讨吧。我们要找的点是矩形内使\(ax+by\)最小的点和最大的点,所以两维是独立的,分讨不是特别困难

P3769

四维啊,有点意思
人傻了,竟然不会做了
最长上升子序列不是dp吗?这个都想不到?什么实力啊?
所以我们按照第一维排序,然后每次就是三维偏序的KDtree,但是题解明显有更厉害的东西。

我们建出二维的KDtree,然后最外面的一维是树状数组,所以我们一共有\(n\)颗KDtree,空间一只\(log\),然后查询就是查log颗KDtree,因为单个复杂度是\(O(\sqrt{n})\)的,我们发现总的\(O(\sum \sqrt{2^{i}})=O(\sqrt{n})\)然后就做完了,其实还是很妙的,爆踩别的算法,其他算法都是路边一条。
四维偏序记得前面排序要四个关键字,否则第一位一样,第二维万一不是正序,那么就寄了。

P4169

经典曼哈顿,我们维护4个max即可。但是!如果我们使用树状数组呢?那么我们要在线维护,有点难啊,这下要上CDQ了。我们惊奇的发现,这个动态,然后我们查询是\(\sqrt{n}log_{n}\)的,因为我们建树要花时间。那么怎么办呢?我们可不可以这样。我们先把树建出来,然后每次单点修改,然后就好了。
复健,出的错有点多的,注意

P6247

幽默题
cout是保留6位小数啊,真的幽默了。

P10459

我不到啊,这是两个部分的KDtree。乱搞吧。
这个人把\(coscos-sinsin\)背错了,真的菜死了。

posted @ 2025-07-09 10:09  wuhupai  阅读(29)  评论(0)    收藏  举报