\(K-D Tree\),一种用来维护\(K\)维数据的数据结构。常用于维护各种高维的数据,或者是邻近搜索等。从另一种意义上说,实际上就是高维的二叉搜索树。对于一些常见的问题,如\(k\)远点对、三位偏序(在线)等,可以用\(K-DTree\)解决。

那么,\(KDT\)如何建树呢?

观察一下普通的二叉搜索树,发现它的每一个节点都有一个“关键字",用它来划分左右儿子。于是,对于高维的数据,我们考虑分层划分。

比如对于点\((x,y)\),它有两层维度。那么,我们可以在第一层用\(x\)划分,第二层用\(y\)划分,第三层用\(x\)划分,依次迭代。也可以用随机数的方法,随机划分。

对于第\(i\)层维度,我们对它的维度编号是\(i-1\).

接下来考虑如何实现建树。

首先,这个树上得维护一些东西,要不然就没啥意义了。对于一个节点,我们首先要知道它所管辖的区域有哪些。那么对于\(k\)维的数据,我们定义树中维护两个数组:

\(int\) \(mi[k],mx[k]\)来维护每一层它所能维护到的上界和下界。

其次,我们可以用\(siz\)来维护它的子树大小(用于重构),以及题目中要求的信息。

对于区间\([l,r]\),我们依旧是按照套路,将它划分为左右两个区间,并分别递归。

注意选择每个点中管辖的区域中哪个点做根的时候,比较优的是中位数。于是,我们建树的时候,就优先使用中位数来做这个孩子的点,就是所谓这颗子树的\(rt\)点。

那么,怎么找中位数呢?\(k\)维,每一层都做一次\(sort\),那不得炸上天?

这样的复杂度是\(O(knlogn)\)的(一本正经地瞎蒙)

所以我们考虑换一个方式。\(STL\)自然会给我们另一条路的:\(nth\)_\(element\)函数就可以满足我们的需求。

它可以做到对于一个\([l,r]\)的序列,用\(O(n)\)的时间,使得我们所选择的的那个位置\(pos\)上处于合适的数字。但是注意,除了这个位置上,其它位置上没有被排序。

但是我们只需要这么多就够了,不是吗?

用它的时候,别忘了重载一下运算符。

于是,\(build\)建树的代码就呼之欲出了……

int build(int l,int r,int d){
if(l>r)return 0;
int x=++tot,mid=l+r>>1;
D=d;nth_element(p+l,p+mid,p+r+1);
tr[x].c=p[mid];ls[x]=build(l,mid-1,d^1);
rs[x]=build(mid+1,r,d^1);pushup(x);return x;
}

还记得当初学那些什么乱七八糟的平衡树的时候,总会有不平衡的情况。同样地,\(KDT\)也可以支持动态插入,而不平衡也是我们要解决的问题之一。

相信各位听过一句话:优美的暴力(逃)

记得有个东西叫替罪羊树吗,里面有个东东叫重构。我们引入它的概念,来实现\(KDT\)的平衡。

设定平衡因子\(Alpha\),来检测是否不平衡,不平衡则重建。记住,数据维度要对应上。

对于空间,开一个垃圾桶(雾),保存删掉的节点的编号,循环利用一下。

下面给出这部分代码:

inline int New(){
if(top)return rub[top--];
else return ++tot;
}
inline int build(int l,int r,int d){
if(l>r)return 0;
int x=New(),mid=l+r>>1;
D=d;nth_element(p+l,p+mid,p+r+1);
tr[x].c=p[mid];ls[x]=build(l,mid-1,d^1);
rs[x]=build(mid+1,r,d^1);pushup(x);return x;
}
inline void clear(int x,int pos){
if(ls[x])clear(ls[x],pos);
p[pos+tr[ls[x]].siz+1]=tr[x].c;rub[++top]=x;
if(rs[x])clear(rs[x],pos+tr[ls[x]].siz+1);
}
inline void check(int &x,int d){
double C=A*(double)(tr[x].siz);
if(C<(double)(tr[ls[x]].siz)||C<(double)(tr[rs[x]].siz)){
clear(x,0);
x=build(1,tr[x].siz,d);
}
}
inline void Ins(int &x,point p,int d){
if(!x){x=New();ls[x]=rs[x]=0;tr[x].c=p;pushup(x);return;}
if(p.x[d]<=tr[x].c.x[d])Ins(ls[x],p,d^1);
else Ins(rs[x],p,d^1);
pushup(x);check(x,d);
}

里面的\(A\)就是阿尔法。

好了,现在看看它都(干了些什么)能干什么吧。

例题:\(K\)远点对#

求出\(n\)个点中,对于指定点,第\(k\)大的欧氏距离的平方。

这玩意,用一个堆和\(KDT\)结合就好了。

对于第\(k\)大,堆里面\(push\)进\(2k\)个0.因为,对于每个点,我找到的都是不定向的,所以同一个点会两次被搜到。于是,堆要到\(2k\).

对于每一次搜到的答案,和堆顶比较,如果大就放进去,并去掉堆尾,并以每一次的答案去搜索左右子树(没错,它是靠剪枝吃饭哒)

对于一个点到管辖范围的查询……意会即可(雾

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
using namespace std;
#define inf 192608170000000ll
typedef long long ll;
ll read(){
ll x=0,pos=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') pos=0;
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
return pos?x:-x;
}
const ll MAXN=2e5+10;
ll n,k;
struct point{
ll x[2];
}p[MAXN];
struct cmp{
ll operator()(ll a,ll b){
return a>b;
}
};
priority_queue<ll,vector<ll>,cmp>q;
struct node{
ll minn[2],maxn[2],siz;
point c;
}tr[MAXN];
ll rt,D,rs[MAXN],ls[MAXN];
ll operator<(point a,point b){return a.x[D]<b.x[D];}
inline void pushup(ll x){
ll l=ls[x],r=rs[x];
tr[x].siz=tr[l].siz+tr[r].siz+1;
for(register int i=0;i<=1;++i){
tr[x].minn[i]=tr[x].maxn[i]=tr[x].c.x[i];
if(l)tr[x].minn[i]=min(tr[x].minn[i],tr[l].minn[i]),tr[x].maxn[i]=max(tr[x].maxn[i],tr[l].maxn[i]);
if(r)tr[x].minn[i]=min(tr[x].minn[i],tr[r].minn[i]),tr[x].maxn[i]=max(tr[x].maxn[i],tr[r].maxn[i]);
}
}
ll tot;
void build(ll &x,ll l,ll r,ll d){
if(l>r)return;
x=++tot;
ll mid=l+r>>1;
D=d;nth_element(p+l,p+mid,p+r+1);
tr[x].c=p[mid];
build(ls[x],l,mid-1,d^1);
build(rs[x],mid+1,r,d^1);
pushup(x);
}
inline ll abs(ll a){return a>0?a:-a;}
ll dis(point a,point b){return (a.x[0]-b.x[0])*(a.x[0]-b.x[0])+(a.x[1]-b.x[1])*(a.x[1]-b.x[1]);}
ll dissqr(point top,ll a){
ll di=0;
for(int i=0;i<=1;++i){
ll nd=0;
if(top.x[i]<tr[a].minn[i])
nd=tr[a].maxn[i]-top.x[i];
else if(top.x[i]>tr[a].maxn[i])
nd=top.x[i]-tr[a].minn[i];
else nd=max(top.x[i]-tr[a].minn[i],tr[a].maxn[i]-top.x[i]);
di+=nd*nd;
}
return di;
}
void query(ll x,point top){
ll di=dis(tr[x].c,top);
if(di>q.top())q.pop(),q.push(di);
ll l=ls[x],r=rs[x],dl,dr;
dl=l?dissqr(top,l):-inf,dr=r?dissqr(top,r):-inf;
if(dl>dr){
if(dl>q.top())query(l,top);
if(dr>q.top())query(r,top);
}
else{
if(dr>q.top())query(r,top);
if(dl>q.top())query(l,top);
}
}
int main(){
n=read(),k=read();
for(int i=1;i<=n;++i)p[i].x[0]=read(),p[i].x[1]=read();
build(rt,1,n,0);
for(int i=1;i<=k+k;++i)q.push(0);
for(int i=1;i<=n;++i)query(rt,p[i]);
printf("%lld\n",q.top());
return 0;
}

讲了一下kdt的重构等,也没多少啊qwq,留几个题吧

\(Luogu-P2479\)

\(Luogu-P4357\)

\(Luogu-P4475\)

\(Luogu-P4169\)

\(Luogu-P4390\)

\(Luogu-P4148\)

\(Luogu-P3769\)

够了不qwq

题解2479

题解4475

题解4148

题解3769

K-DTree入门的更多相关文章

  1. django模型操作

    Django-Model操作数据库(增删改查.连表结构) 一.数据库操作 1.创建model表        

  2. 主席树入门(区间第k大)

    主席树入门 时隔5个月,我又来填主席树的坑了,现在才发现学算法真的要懂了之后,再自己调试,慢慢写出来,如果不懂,就只会按照代码敲,是不会有任何提升的,都不如不照着敲. 所以搞算法一定要弄清原理,和代码 ...

  3. web安全之机器学习入门——3.1 KNN/k近邻

    目录 sklearn.neighbors.NearestNeighbors 参数/方法 基础用法 用于监督学习 检测异常操作(一) 检测异常操作(二) 检测rootkit 检测webshell skl ...

  4. A - 低阶入门膜法 - K-th Number (主席树查询区间第k小)

    题目链接:https://cn.vjudge.net/contest/284294#problem/A 题目大意:主席树查询区间第k小. 具体思路:主席树入门. AC代码: #include<i ...

  5. [机器学习]-K近邻-最简单的入门实战例子

    本篇文章分为两个部分,前一部分主要简单介绍K近邻,后一部分是一个例子 第一部分--K近邻简介 从字面意思就可以容易看出,所谓的K近邻,就是找到某个样本距离(这里的距离可以是欧式距离,曼哈顿距离,切比雪 ...

  6. (数据挖掘-入门-3)基于用户的协同过滤之k近邻

    主要内容: 1.k近邻 2.python实现 1.什么是k近邻(KNN) 在入门-1中,简单地实现了基于用户协同过滤的最近邻算法,所谓最近邻,就是找到距离最近或最相似的用户,将他的物品推荐出来. 而这 ...

  7. 数据挖掘入门系列教程(三)之scikit-learn框架基本使用(以K近邻算法为例)

    数据挖掘入门系列教程(三)之scikit-learn框架基本使用(以K近邻算法为例) 简介 scikit-learn 估计器 加载数据集 进行fit训练 设置参数 预处理 流水线 结尾 数据挖掘入门系 ...

  8. (算法入门经典大赛 优先级队列)LA 3135(之前K说明)

    A data stream is a real-time, continuous, ordered sequence of items. Some examples include sensor da ...

  9. 主席树入门——询问区间第k大pos2104,询问区间<=k的元素个数hdu4417

    poj2104找了个板子..,但是各种IO还可以进行优化 /* 找区间[l,r]第k大的数 */ #include<iostream> #include<cstring> #i ...

  10. Python3入门机器学习 - k近邻算法

    邻近算法,或者说K最近邻(kNN,k-NearestNeighbor)分类算法是数据挖掘分类技术中最简单的方法之一.所谓K最近邻,就是k个最近的邻居的意思,说的是每个样本都可以用它最接近的k个邻居来代 ...

随机推荐

  1. Tomact的中文乱码设置

    在使用Tomact时,有时候使用中文时,窗口会把中文部分显示为乱码,这时需要修改相关配置,让其正常显示. 1.修改server.xml的配置,解决显示窗口的乱码 打开Tomcat下/bin/serve ...

  2. java初探(1)之登录初解

    初识登录 登录的应用场景 登录比较常见,大多数网站都有登录的操作.然后登录本身也从简单到复杂有着漫长的发展历史.本文记录博主对登录的应用场景的剖析,深究不在于学习如何实现,主要关注其编码思想,过程中用 ...

  3. Java中nextInt和nextLine同时使用出现的问题

    代码: package com.ins1; import java.util.*; public class test { public static void main(String[] args) ...

  4. leetcode刷题-58最后一个单词

    题目 给定一个仅包含大小写字母和空格 ' ' 的字符串 s,返回其最后一个单词的长度.如果字符串从左向右滚动显示,那么最后一个单词就是最后出现的单词. 如果不存在最后一个单词,请返回 0 . 说明:一 ...

  5. JS实现串行请求

    使用async和await var fn = async function(promiseArr) { for(let i = 0,len = arr.length; i<len; i++) { ...

  6. ansible使用,常用模块

    使用ansible管理其他主机有两种方式: 1.命令行执行ansible ad-hoc命令 2.把要做的动作行为写入一个文件[playbook脚本],ansible读取脚本自动完成相应的任务. Ans ...

  7. 2020重新出发,JAVA高级,JVM种设计模式

    Java的23种设计模式全面解析 设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路.它不是语法规定,而是一套用来提高代码可复用性.可维护性.可读性.稳健 ...

  8. java Spring系列之 配置文件的操作 +Bean的生命周期+不同数据类型的注入简析+注入的原理详解+配置文件中不同标签体的使用方式

    Spring系列之 配置文件的操作 写在文章前面: 本文带大家掌握Spring配置文件的基础操作以及带领大家理清依赖注入的概念,本文涉及内容广泛,如果各位读者耐心看完,应该会对自身有一个提升 Spri ...

  9. Linux内存子系统——Locking Pages(内存锁定)

    该部分内容可以参考libc man page 3.5 LockingPages 概述 你可以让系统将特定的虚拟内存页与实际页帧相"关联",并保持这样的状态(称为锁定).该部分内存不 ...

  10. 配置静态 IP、网卡命名规范

    一.网卡命名规范(设备类型 + 设备位置 + 数字) 设备类型: 格式 描述 en 以太网(Ethernet) ib 无限宽带(InfiniBand) sl 串列线路互联网协议(slip:Serial ...