考 \(NOI\) 时不会,感觉很亏。于是学了一上午,写了一晚上。

感觉这东西就是个复杂度玄学的高级暴力 (大雾

KD-tree 基本信息

\(D\) 就是 \(Dimension\) ,维度的意思。 \(KD-tree\) 就是用来解决多维点的问题。

它可以说是一棵平衡树,但建树时用来比较的东西很奇特。

查询几乎与线段树类似,但更暴力一些。每次查询的复杂度为 \(O(n^{\frac{k-1}{k}})\)

最常见的时平面上点的问题,也就是 \(2D-tree\)

有时它的功能可被树套树或 \(CDQ\) 分治实现,它的时间复杂度不如那两者优。

但是它写起来容易,而且在限制空间或在线时是很好的选择。


基本操作

  • 建树 \(build\)

大体思路与平衡树类似,但特别的地方是权值的比较——各个维度轮流作为关键字来比较。

所以在建树的过程中需要一个变量来记当前以哪一维为关键字。

之后我们要找一个根节点,然后比根节点小的扔到左子,大的扔到右子,分别递归

那为保证树的平衡,根节点就是中位数咯

\(STL\) 有一个高级函数 \(nth \_ element()\) 可以在 \(O(n)\) 实现找第 \(k\) 小的点,并将比它小或大的点分居在它两侧。

借助它就能在 \(O(Knlogn)\) 完成建树了。

  • 插入 \(insert\)

与平衡树的插入类似,就是按照比较权值的方法,小的往左子走,大的往右子走。

  • 拍扁重构 \(check+pia+build\)

为了保证树的平衡,类似替罪羊树,在插入后判断树是否失衡(即左子或右子的 \(size\) 过大)

如果失衡,就暴力重构这个子树。

具体地,就是先遍历一遍,把子树中所有点都拿出来。然后对这个子树 \(build\)

注意空间的回收利用,可以用一个栈保存删掉的点的地址。

  • 查询 \(query\)

与线段树类似,但非常非常暴力。

若整个区间满足条件,就用维护好的有关整个区间的东西;不满足,就分别询问当前点及两个子树。

需要用到剪枝、乱搞等技巧。

首先,为了查询方便,每个节点要记录子树的范围,即子树中各维度的 \(mn\) 和 \(mx\)

然后可以设置估值函数。

根据估值函数的大小我们可以决定进不进某个子树、先进哪个子树等等……


模板

洛谷 \(P4169\) \([Violet]天使玩偶/ SJY 摆棋子\)

求最小曼哈顿距离。

#include<cstdio>
#include<iostream>
#include<algorithm> #define INF 10000000 using namespace std; int read(){
int x=0;
char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x;
} const int N = 300005; int n,D;
struct data {
int d[2];
data() { d[0]=d[1]=0; }
data(int x,int y) { d[0]=x; d[1]=y; }
bool operator < (const data &b) const{ return d[D]<b.d[D]; }
}a[N],b[N*2];
int tot; struct tree{
tree *ch[2];
int mx[2],mn[2],d[2],sz;
}pool[N*2],*st[N*2],*root;
int cnt,top;
tree *New() { return top?st[--top]:&pool[++cnt]; }
int mn(tree *p,int i) { return p ? p->mn[i] : INF ; }
int mx(tree *p,int i) { return p ? p->mx[i] : -INF ; }
int sz(tree *p) { return p ? p->sz : 0 ; }
void update(tree *p){
for(int i=0;i<2;i++){
p->mx[i]=max(p->d[i],max(mx(p->ch[0],i),mx(p->ch[1],i)));
p->mn[i]=min(p->d[i],min(mn(p->ch[0],i),mn(p->ch[1],i)));
}
p->sz=1+sz(p->ch[0])+sz(p->ch[1]);
}
tree *build(data A[],int l,int r,int de){
if(l>r) return NULL;
tree *p=New();
int mid=(l+r)>>1;
D=de; nth_element(A+l,A+mid,A+r);
p->d[0]=A[mid].d[0]; p->d[1]=A[mid].d[1];
p->ch[0]=build(A,l,mid-1,de^1);
p->ch[1]=build(A,mid+1,r,de^1);
update(p);
return p; //别忘了返回值!
}
void pia(tree *p){
if(p->ch[0]) pia(p->ch[0]);
st[top++]=p;
b[++tot].d[0]=p->d[0]; b[tot].d[1]=p->d[1];
if(p->ch[1]) pia(p->ch[1]);
}
tree *check(tree *p,int de){
if(1.0*sz(p->ch[0])<=0.75*p->sz && 1.0*sz(p->ch[1])<=0.75*p->sz) return p;
tot=0; pia(p);
return build(b,1,tot,de);
}
void insert(tree *p,tree *nd,int de){
int f=nd->d[de]>p->d[de];
if(!p->ch[f]) p->ch[f]=nd;
else insert(p->ch[f],nd,de^1);
p->ch[f]=check(p->ch[f],de^1);
update(p);
}
int getdis(tree *p,data x){
if(!p) return INF;
int ret=0;
for(int i=0;i<2;i++) ret+=max(0,p->mn[i]-x.d[i])+max(0,x.d[i]-p->mx[i]);
return ret;
}
int ans;
void query(tree *p,data x){
if(!p) return;
ans=min(ans,abs(p->d[0]-x.d[0])+abs(p->d[1]-x.d[1]));
int d0=getdis(p->ch[0],x),d1=getdis(p->ch[1],x);
if(d0>=ans && d1>=ans) return;
if(d0<d1){
query(p->ch[0],x);
if(d1<ans) query(p->ch[1],x);
}
else{
query(p->ch[1],x);
if(d0<ans) query(p->ch[0],x);
}
} int main()
{
int m,t,x,y;
n=read(); m=read();
for(int i=1;i<=n;i++){
a[i].d[0]=read();
a[i].d[1]=read();
} root=build(a,1,n,0);
while(m--){
t=read(); x=read(); y=read();
if(t==1){
tree *nd=New();
nd->sz=1;
nd->mn[0]=nd->mx[0]=nd->d[0]=x;
nd->mn[1]=nd->mx[1]=nd->d[1]=y;
insert(root,nd,0); root=check(root,0);
}
else{<C
ans=INF;
data c(x,y);
query(root,c);
printf("%d\n",ans);
}
} return 0;
}

洛谷 \(P4475 巧克力王国\)

没有新插入的点,所以不需要拍扁重构。

查询时临界点就是4个端点:

若它们中最大的 \(<C\) ,则全部满足;若最小的 \(\geq C\) ,则全不满足。

其实这也等价于判断4个点中有几个满足 \(<C\) ,若4个,则全部满足;若0个,则全不满足。

我嫌指针版常数过大,换了数组版,写着或看着都好清爽呀~

注意 \(long\) \(long\)

#include<cstdio>
#include<iostream>
#include<algorithm> #define INF 1000000005
#define ll long long using namespace std; int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch) && ch!='-') ch=getchar();
if(ch=='-') f=-1,ch=getchar();
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x*f;
}
ll lread(){
int f=1;
ll x=0;
char ch=getchar();
while(!isdigit(ch) && ch!='-') ch=getchar();
if(ch=='-') f=-1,ch=getchar();
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x*f;
} const int N = 500005; int D;
struct data{
int d[2],val,id;
data() { d[0]=d[1]=val=id=0; }
data(int x,int y,int c,int i) { d[0]=x; d[1]=y; val=c; id=i; }
bool operator < (const data &b) const{ return d[D]<b.d[D]; }
}a[N]; int ch[N][2],mx[N][2],mn[N][2],d[N][2],root;
ll val[N],sum[N];
void update(int x){
for(int i=0;i<2;i++){
mx[x][i]=max(d[x][i],max(mx[ch[x][0]][i],mx[ch[x][1]][i]));
mn[x][i]=min(d[x][i],min(mn[ch[x][0]][i],mn[ch[x][1]][i]));
}
sum[x]=val[x]+sum[ch[x][0]]+sum[ch[x][1]];
}
int build(int l,int r,int ty){
if(l>r) return 0;
int mid=(l+r)>>1;
D=ty; nth_element(a+l,a+mid,a+r+1);
int x=a[mid].id;
ch[x][0]=build(l,mid-1,ty^1);
ch[x][1]=build(mid+1,r,ty^1);
update(x);
return x;
}
ll ans,A,B,C;
ll cal(int x,int y) { return A*x+B*y; }
void ask(int x){
if(!x) return;
int t=0;
if(cal(mn[x][0],mn[x][1])<C) t++;
if(cal(mn[x][0],mx[x][1])<C) t++;
if(cal(mx[x][0],mn[x][1])<C) t++;
if(cal(mx[x][0],mx[x][1])<C) t++;
if(t==0) return;
if(t==4) { ans+=sum[x]; return; }
if(cal(d[x][0],d[x][1])<C) ans+=val[x];
ask(ch[x][0]); ask(ch[x][1]);
} int main()
{
int n,m;
n=read(); m=read();
for(int i=1;i<=n;i++){
d[i][0]=read(); d[i][1]=read(); val[i]=lread();
a[i]=data(d[i][0],d[i][1],val[i],i);
}
for(int i=0;i<=n;i++) {
ch[i][0]=ch[i][1]=0;
mx[i][0]=mx[i][1]=-INF; mn[i][0]=mn[i][1]=INF;
sum[i]=0;
}
root=build(1,n,0); while(m--){
A=lread(); B=lread(); C=lread();
ans=0;
ask(root);
printf("%lld\n",ans);
} return 0;
}

KD-tree 学习小记的更多相关文章

  1. k-d tree 学习笔记

    以下是一些奇怪的链接有兴趣的可以看看: https://blog.sengxian.com/algorithms/k-dimensional-tree http://zgjkt.blog.uoj.ac ...

  2. K-D Tree学习笔记

    用途 做各种二维三维四维偏序等等. 代替空间巨大的树套树. 数据较弱的时候水分. 思想 我们发现平衡树这种东西功能强大,然而只能做一维上的询问修改,显得美中不足. 于是我们尝试用平衡树的这种二叉树结构 ...

  3. kd tree学习笔记 (最近邻域查询)

    https://zhuanlan.zhihu.com/p/22557068 http://blog.csdn.net/zhjchengfeng5/article/details/7855241 KD树 ...

  4. [学习笔记]K-D Tree

    以前其实学过的但是不会拍扁重构--所以这几天学了一下 \(K-D\ Tree\) 的正确打开姿势. \(K\) 维 \(K-D\ Tree\) 的单次操作最坏时间复杂度为 \(O(k\times n^ ...

  5. 【学习笔记】K-D tree 区域查询时间复杂度简易证明

    查询算法的流程 如果查询与当前结点的区域无交集,直接跳出. 如果查询将当前结点的区域包含,直接跳出并上传答案. 有交集但不包含,继续递归求解. K-D Tree 如何划分区域 可以借助下文图片理解. ...

  6. k-d tree算法

    k-d树(k-dimensional树的简称),是一种分割k维数据空间的数据结构.主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索). 应用背景 SIFT算法中做特征点匹配的时候就会利用到k ...

  7. [转载]kd tree

    [本文转自]http://www.cnblogs.com/eyeszjwang/articles/2429382.html k-d树(k-dimensional树的简称),是一种分割k维数据空间的数据 ...

  8. 【数据结构与算法】k-d tree算法

    k-d tree算法 k-d树(k-dimensional树的简称),是一种分割k维数据空间的数据结构.主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索). 应用背景 SIFT算法中做特征点 ...

  9. P4169-CDQ分治/K-D tree(三维偏序)-天使玩偶

    P4169-CDQ分治/K-D tree(三维偏序)-天使玩偶 这是一篇两种做法都有的题解 题外话 我写吐了-- 本着不看题解的原则,没写(不会)K-D tree,就写了个cdq分治的做法.下面是我的 ...

  10. AOJ DSL_2_C Range Search (kD Tree)

    Range Search (kD Tree) The range search problem consists of a set of attributed records S to determi ...

随机推荐

  1. PowerShell 拿到显卡信息

    本文告诉大家如何在 PowerShell 通过 WMI 拿到显卡信息 在 PowerShell 可以使用下面代码拿到显卡的信息 Get-WmiObject Win32_VideoController ...

  2. 2019-8-31-dotnet-删除只读文件

    title author date CreateTime categories dotnet 删除只读文件 lindexi 2019-08-31 16:55:58 +0800 2019-02-28 1 ...

  3. pycharm 更改创建文件默认路径

    1.操作 依次找到以下路径修改为自己想要的路径即可:PyCharm——>Settings——>Appearance&Behavior——>System Setting——&g ...

  4. 23.logging

    转载:https://www.cnblogs.com/yuanchenqi/article/5732581.html 一 (简单应用) import logging logging.debug('de ...

  5. MindManager使用技巧

    任务窗格在右下角. 1.条件的设置与编辑 2.优先级视图 不是优先级图标  3.圆圈图 4.洋葱图的使用 右键选择背景再选择解锁所有背景就可以对所有圆形进行调整了 点图形边缘出现十字架可以进行移动 5 ...

  6. 第三阶段:3.Web端产品设计:5.产品设计-视觉设计

    视觉设计主要在表现层. 色彩心理产品经理可以也是应当掌握的.什么颜色的选择都是有理有据的. 信息清晰度. 比如这个图:当用户操作出问题,谷歌会给出问题同时给出解决方法. 视觉动物. 2/8分布原则.用 ...

  7. Your development team, "", does not support the Push Notifications capability.

    问题: Your development team, "", does not support the Push Notifications capability. 解决方法: 1 ...

  8. 如何把对象手动注入Spring容器并实现依赖注入

    将对象注入到Spring容器并实现依赖注入 public class UserDao { @Resource AccountService accountService; public void pr ...

  9. Python 元类的基本介绍及使用

    一.什么是元类 二.如何使用元类 (一)不依赖class关键字创建一个自定义类 (二)自定义元类控制类的产生 (三)自定义元类控制类的调用 一.什么是元类 在python中,一切皆对象,而对象都是由类 ...

  10. $CF559C\ Gerald\ and\ Fiant\ Chess$ 计数类$DP$

    AcWing Description 有个$H$行$W$列的棋盘,里面有$N$个黑色格子,求一个棋子由左上方格子走到右下方格子且不经过黑色格子的方案数. $1<=H,M<=1e5,1< ...