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 ...
随机推荐
- Linux 内核列举设备和驱动
如果你在编写总线级别的代码, 你可能不得不对所有已经注册到你的总线的设备或驱动进 行一些操作. 它可能会诱惑人直接进入 bus_type 结构中的各种结构, 但是最好使用已经 提供的帮助函数. 为操作 ...
- Linux USB 的 Urbs
linux 内核中的 USB 代码和所有的 USB 设备通讯使用称为 urb 的东西( USB request block). 这个请求块用 struct urb 结构描述并且可在 include/l ...
- ZR9.8普转提
ZR9.8普转提 A,B 打过的CF原题,不管了 C 确认过眼神,是我不会写的DP, 发现这个题目要求的过程类似与一个所有括号都不一样的括号匹配的过程 但是限制条件非常多,有点无从下手的感觉 我们设\ ...
- ERROR StatusLogger Log4j2 could not find a logging implementation.
今天在学习structs2 2.5.5的版本的时候碰到2个问题.第一个网上下的包里面差log4j-core这个包. 虽然程序可以运行,但控制台会报这个错误. ERROR StatusLogger L ...
- 牛客训练赛55 E 树
很妙的一个树形DP问题,简单考虑了一下就过了 https://ac.nowcoder.com/acm/contest/2927/E 主要就是推公式(公式有点长呀) 大概就是这样,其实挺简单的. #in ...
- Vim编辑器Go简单入门
今天是一次做Go的笔记,一开始直接打开Github上的Go项目然后跑到Wiki位置,然后作者列出了一堆学习Go的资料,这里我 以第一个学习资料https://tour.golang.org/作为Go学 ...
- spring boot中表单验证的使用
一.前言 为啥子要搞这个表单验证呢?答案简单而现实,举个栗子,你辛辛苦苦的写了一个录入个人信息的功能,比如年龄这个位置,用户就没看到一下子写了个性别男,一提交,直接报错了,是不是很尴尬呢, 作为一个测 ...
- Kafka 集群在马蜂窝大数据平台的优化与应用扩展
马蜂窝技术原创文章,更多干货请订阅公众号:mfwtech Kafka 是当下热门的消息队列中间件,它可以实时地处理海量数据,具备高吞吐.低延时等特性及可靠的消息异步传递机制,可以很好地解决不同系统间数 ...
- 洛谷$P1600$ 天天爱跑步 树上差分
正解:树上差分 解题报告: 传送门$QwQ$! 这题还挺妙的,,,我想了半天才会$kk$ 首先对一条链$S-T$,考虑先将它拆成$S-LCA$和$LCA-T$,分别做.因为总体上来说差不多接下来我就只 ...
- 【一起学源码-微服务】Nexflix Eureka 源码五:EurekaClient启动要经历哪些艰难险阻?
前言 在源码分析三.四都有提及到EurekaClient启动的一些过程.因为EurekaServer在集群模式下 自己本身就是一个client,所以之前初始化eurekaServerContext就有 ...