KD-tree 学习小记
考 \(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 学习小记的更多相关文章
- k-d tree 学习笔记
以下是一些奇怪的链接有兴趣的可以看看: https://blog.sengxian.com/algorithms/k-dimensional-tree http://zgjkt.blog.uoj.ac ...
- K-D Tree学习笔记
用途 做各种二维三维四维偏序等等. 代替空间巨大的树套树. 数据较弱的时候水分. 思想 我们发现平衡树这种东西功能强大,然而只能做一维上的询问修改,显得美中不足. 于是我们尝试用平衡树的这种二叉树结构 ...
- kd tree学习笔记 (最近邻域查询)
https://zhuanlan.zhihu.com/p/22557068 http://blog.csdn.net/zhjchengfeng5/article/details/7855241 KD树 ...
- [学习笔记]K-D Tree
以前其实学过的但是不会拍扁重构--所以这几天学了一下 \(K-D\ Tree\) 的正确打开姿势. \(K\) 维 \(K-D\ Tree\) 的单次操作最坏时间复杂度为 \(O(k\times n^ ...
- 【学习笔记】K-D tree 区域查询时间复杂度简易证明
查询算法的流程 如果查询与当前结点的区域无交集,直接跳出. 如果查询将当前结点的区域包含,直接跳出并上传答案. 有交集但不包含,继续递归求解. K-D Tree 如何划分区域 可以借助下文图片理解. ...
- k-d tree算法
k-d树(k-dimensional树的简称),是一种分割k维数据空间的数据结构.主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索). 应用背景 SIFT算法中做特征点匹配的时候就会利用到k ...
- [转载]kd tree
[本文转自]http://www.cnblogs.com/eyeszjwang/articles/2429382.html k-d树(k-dimensional树的简称),是一种分割k维数据空间的数据 ...
- 【数据结构与算法】k-d tree算法
k-d tree算法 k-d树(k-dimensional树的简称),是一种分割k维数据空间的数据结构.主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索). 应用背景 SIFT算法中做特征点 ...
- P4169-CDQ分治/K-D tree(三维偏序)-天使玩偶
P4169-CDQ分治/K-D tree(三维偏序)-天使玩偶 这是一篇两种做法都有的题解 题外话 我写吐了-- 本着不看题解的原则,没写(不会)K-D tree,就写了个cdq分治的做法.下面是我的 ...
- 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 ...
随机推荐
- 【30.43%】【codeforces 746C】Tram
time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...
- VMware下配置Linux IP,解决Linux ping不通
因为安装好VMware8.0后,把VMware服务都设成手动的了,导致有些功能不好使,费了半天劲, 如果安装Linux时选择DHCP自动分配IP,需要启动服务: VMware DHCP service ...
- codeforces 1167B Lost Numbers
传送门:https://codeforces.com/contest/1167/problem/B 题意: 交互题:现在你有6个数4, 8, 15, 16, 23, 42组成的某种组合,你可以询问系统 ...
- Hibernate映射文件详解(News***.hbm.xml)一
Hibernate是一个彻底的ORM(Object Relational Mapping,对象关系映射)开源框架. 我们先看一下官方文档所给出的,Hibernate 体系结构的高层视图: 其中PO=P ...
- ERROR StatusLogger Log4j2 could not find a logging implementation.
今天在学习structs2 2.5.5的版本的时候碰到2个问题.第一个网上下的包里面差log4j-core这个包. 虽然程序可以运行,但控制台会报这个错误. ERROR StatusLogger L ...
- 叶子的颜色---经典树上dp
挺简单的一个dp #include<iostream> #include<cstring> #include<cstdio> #include<algorit ...
- Mongdb的基本操作及java中用法
Mongdb中所有数据以Bson(类似JSON)的格式存在,可以存储集合,map,二进制文件等多种数据类型. 数据库的常用操作 use [数据库名称];//有就选中,没有就添加并选中show dbs; ...
- $Poj3017\ Cut\ The\ Sequence$ 单调队列优化$DP$
Poj AcWing Description 给定一个长度为N的序列 A,要求把该序列分成若干段,在满足“每段中所有数的和”不超过M的前提下,让“每段中所有数的最大值”之和最小. N<=10 ...
- Python 打包的现状:包的三种类型
英文 | The state of Python Packaging[1] 原作 | BERNAT GABOR 译者 | 豌豆花下猫 声明 :本文获得原作者授权翻译,转载请保留原文出处,请勿用于商业或 ...
- 【一起学源码-微服务】Ribbon 源码一:Ribbon概念理解及Demo调试
前言 前情回顾 前面文章已经梳理清楚了Eureka相关的概念及源码,接下来开始研究下Ribbon的实现原理. 我们都知道Ribbon在spring cloud中担当负载均衡的角色, 当两个Eureka ...