KD树小结

很久之前我就想过怎么快速在二维平面上查找一个区域的信息,思考许久无果,只能想到几种优秀一点的暴力。

KD树就是干上面那件事的。

别的不多说,赶紧把自己的理解写下来,免得凉了。

 

KD树的组成

以维护k维空间(x,y,……)内的KD树为例,主要由一下三部分组成:

  1. p[k],代表树上这个结点所储存的点(在题目中给出的/你自己加上的点集中的一个点)。
  2. ch[2],表示它的子结点(没错,KD树是一棵二叉树)
  3. mi[k]与mx[k],mi/mx[i]代表KD树这个结点统辖的所有点的第i-1范围。比如说mi[1]=2,mx[1]=4,就代表这棵树统辖的点的y坐标都在[2,4]内。

 

不看mi和mx,长得就和splay/trie树一样,一个p维护当前节点,一个ch[2]记录左右儿子。

不看p[k],长得就和线段树一样,有左右儿子和区间信息。

没错,KD树功能如线段树,结点维护区域信息;形态如splay/trie树,每个结点有实际的值和意义。

 

KD树的构建

一般题目都是二维平面。下面就以二维平面KD树的构建为例。

读入把点存进结构体数组a中,坐标分别为a[x].p[i]。

inline void build(int &x,int l,int r,int type){x=(l+r)>>1;now=type;nth_element(a+l,a+x,a+r+1,cmp);nd=a[x];newnode(x);if(l<x)build(ch[x][0],l,x-1,type^1);else ch[x][0]=0;if(x<r)build(ch[x][1],x+1,r,type^1);else ch[x][1]=0;pushup(x);
}build(kd.root,1,n,0);

非常优美……对type、now作用不明的同学请继续阅读……你要现在就明白就奇怪了

系统函数nth_element(a+l,a+x,a+r+1),头文件algorithm,需定义<或cmp函数。

作用:把排序后第x大的放到第x位,比它小的放进左边,比它大的放进右边(两边无序)。

注意区间开闭:左闭右开,中间也是闭合的。

复杂度:平均,期望是O(n)?可以接受。

下面给出cmp、newnode、pushup代码。

struct Node{int p[2],mi[2],mx[2];}a[N];
inline bool cmp(const Node &a,const Node &b){return a.p[now]<b.p[now];}
inline void Min(int &x,int y){x=x<y?x:y;}
inline void Max(int &x,int y){x=x>y?x:y;}
inline void pushup(int x){int ls=ch[x][0],rs=ch[x][1];if(ls){Min(T[x].mi[0],T[ls].mi[0]);Max(T[x].mx[0],T[ls].mx[0]);Min(T[x].mi[1],T[ls].mi[1]);Max(T[x].mx[1],T[ls].mx[1]);}if(rs){Min(T[x].mi[0],T[rs].mi[0]);Max(T[x].mx[0],T[rs].mx[0]);Min(T[x].mi[1],T[rs].mi[1]);Max(T[x].mx[1],T[rs].mx[1]);}
}inline void newnode(int x){T[x].p[0]=T[x].mi[0]=T[x].mx[0]=nd.p[0];T[x].p[1]=T[x].mi[1]=T[x].mx[1]=nd.p[1];
}

不要问我为什么辣么长,为了减常冲榜,把循环展开了……

聪明的读者已经发现KD树的构建巧妙之处。它不是纯粹按照x维,或者某一维排序,而是先按x维,再按y维,再按z维,再……最后又回到x维……

这样分割的区域更加整齐划一更加均匀紧缩,不像上面的按照某一维划分,到最后变成一条条长条,KD树划分到底的图案还是很好看的。

这样分割有什么好处呢?等你真正领悟了KD树的精髓之后你就会发现……嘿嘿嘿……

(就是为了把这个暴力数据结构剪枝剪更多跑更快)

 

KD树的操作

1.往KD树上插点

插点可以分为插新点和插老点。如果有老点,特判一句,把信息覆盖即可。

inline void insert(int &x,int type){if(!x){x=++cnt,newnode(cnt);return;}if(nd.p[0]==T[x].p[0] && nd.p[1]==T[x].p[1]){……(自行维护);return;}if(nd.p[type]<T[x].p[type])insert(ch[x][0],type^1);else insert(ch[x][1],type^1);pushup(x);
}

依然非常的美妙……等等有什么不对?

我们能估计出一棵刚建好的KD树深度是O(log)的。

但你这么随便乱插……有道题叫HNOI2017 spaly 插入不旋转的单旋spaly见过?T成苟。

这都不是问题!知不知道有一种数据结构叫做替罪羊树哇?

知道替罪羊树怎么保证复杂度的吗?

重构!大力重构!自信重构!不爽就重构!

为了省事大概每插入10000次就重构一次好了……

if(kd.cnt==sz){for(int i=1;i<=sz;++i)a[i]=kd.T[i];kd.rebuild(kd.root,1,sz,0);sz+=10000;
}

 

2.在KD树上查询

  • 如果是单点(给定点)查询:
    • 太简单啦!把插入改改就阔以辣!
  • 如果是查询距离一个点(x',y')最近的点(曼哈顿距离,|x-x'|+|y-y'|):
    • 首先我们看暴力的剪枝:按某一维排序,如果该维的差过大就不管了。
    • 而令我们期待的KD树呢?呃不好意思,它也是这么做的……
    • 我们维护过两个叫做mi[]和mx[]的东西吧……这个时候就是它派上用场了。
    • 具体还请看代码吧:
      //查询的点(x',y')储存在nd中。
      //这里的l,r就是mi,mx的意思。
      inline int dis(Node p,int x,int ans=0){for(int i=0;i<2;++i)ans+=max(0,t[x].l[i]-p.p[i])+max(0,p.p[i]-t[x].r[i]);return ans;
      }inline void query(int x){Ans=min(Ans,abs(t[x].p[0]-nd.p[0])+abs(t[x].p[1]-nd.p[1]));int dl=ch[x][0]?dis(nd,ch[x][0]):Inf;int dr=ch[x][1]?dis(nd,ch[x][1]):Inf;if(dl<dr){if(dl<Ans)query(ch[x][0]);if(dr<Ans)query(ch[x][1]);}else{if(dr<Ans)query(ch[x][1]);if(dl<Ans)query(ch[x][0]);}
      }
    • dis():如果当前点在这个区间内就是0,否则就是最极的点到它的距离。
    • 聪明绝顶的你已经发现了……这TM就是个暴力。
    • 其实这个数据结构就是一个暴力……
    • 当暴力有了时间复杂度证明……还叫暴力么?读书人的事,能叫偷么?
    • 这么暴力有几个好处:不用枚举所有点;剪枝有效及时。
    • 复杂度有保障,大概在O(√n)级别下,主要看数据。
  • 如果是区间查询,以区间查询点权和为例(之前就有维护好):
    • inline bool in(int l,int r,int xl,int xr){return l<=xl && xr<=r;}
      inline bool out(int l,int r,int xl,int xr){return xr<l || r<xl;}inline int query(int x,int x1,int y1,int x2,int y2){int ans=0;if(!x)return ans;if(in(x1,x2,T[x].mi[0],T[x].mx[0]))if(in(y1,y2,T[x].mi[1],T[x].mx[1]))return T[x].sum;if(out(x1,x2,T[x].mi[0],T[x].mx[0]))return 0;if(out(y1,y2,T[x].mi[1],T[x].mx[1]))return 0;if(in(x1,x2,T[x].p[0],T[x].p[0]))if(in(y1,y2,T[x].p[1],T[x].p[1]))ans+=T[x].val;return ans+query(ch[x][0],x1,y1,x2,y2)+query(ch[x][1],x1,y1,x2,y2);
      }
    • 别看代码长又看起来复杂,写起来跟线段树似的,还是一样的暴力搞。

 

KD树的基本姿势大概就是这个样子……好写不好写错,基本上都是个板子。

附上学长的一(三)句话,从各方面进行了深度总结:

能不写最好还是不要写吧,轻松被卡→_→,也许可以出奇制胜?如果要写,重新构树是个不错的选择。发现大数据跑不过,多半是剪枝挂了。

还是给个链接……MashiroSky大爷。

upd:以当前坐标差最大的来做type应该比轮换type更优秀……

例题有"SJY摆棋子"、"简单题"等,在此就不做赘述了。

比较有意思的应用就是【bzoj3489】 A simple rmq problem,正如上面所言,KD树解决传统数据结构题出奇制胜。

附上"BZOJ4066简单题"代码一份,操作是单点修改+矩形求和在线。

 

#include    <iostream>
#include    <cstdio>
#include    <cstdlib>
#include    <algorithm>
#include    <vector>
#include    <cstring>
#include    <queue>
#include    <complex>
#include    <stack>
#define LL long long int
#define dob double
#define FILE "bzoj_4066"
//#define FILE "简单题"
using namespace std;const int N = 200010;
int n,lst,now,sz=10000;inline int gi(){int x=0,res=1;char ch=getchar();while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();return x*res;
}inline void Min(int &x,int y){x=x<y?x:y;}
inline void Max(int &x,int y){x=x>y?x:y;}
struct Node{int p[2],mi[2],mx[2],val,sum;}a[N];
inline bool cmp(const Node &a,const Node &b){return a.p[now]<b.p[now];}
struct KD_Tree{int ch[N][2],root,cnt;Node T[N],nd;inline void pushup(int x){int ls=ch[x][0],rs=ch[x][1];if(ls){Min(T[x].mi[0],T[ls].mi[0]);Max(T[x].mx[0],T[ls].mx[0]);Min(T[x].mi[1],T[ls].mi[1]);Max(T[x].mx[1],T[ls].mx[1]);}if(rs){Min(T[x].mi[0],T[rs].mi[0]);Max(T[x].mx[0],T[rs].mx[0]);Min(T[x].mi[1],T[rs].mi[1]);Max(T[x].mx[1],T[rs].mx[1]);}T[x].sum=T[x].val;if(ls)T[x].sum+=T[ls].sum;if(rs)T[x].sum+=T[rs].sum;}inline void newnode(int x){T[x].p[0]=T[x].mi[0]=T[x].mx[0]=nd.p[0];T[x].p[1]=T[x].mi[1]=T[x].mx[1]=nd.p[1];T[x].sum=T[x].val=nd.val;}inline void insert(int &x,int type){if(!x){x=++cnt,newnode(cnt);return;}if(nd.p[0]==T[x].p[0] && nd.p[1]==T[x].p[1]){T[x].val+=nd.val;T[x].sum+=nd.val;return;}if(nd.p[type]<T[x].p[type])insert(ch[x][0],type^1);else insert(ch[x][1],type^1);pushup(x);}inline void rebuild(int &x,int l,int r,int type){x=(l+r)>>1;now=type;nth_element(a+l,a+x,a+r+1,cmp);nd=a[x];newnode(x);if(l<x)rebuild(ch[x][0],l,x-1,type^1);else ch[x][0]=0;if(x<r)rebuild(ch[x][1],x+1,r,type^1);else ch[x][1]=0;pushup(x);}inline bool in(int l,int r,int xl,int xr){return l<=xl && xr<=r;}inline bool out(int l,int r,int xl,int xr){return xr<l || r<xl;}inline int query(int x,int x1,int y1,int x2,int y2){int ans=0;if(!x)return ans;if(in(x1,x2,T[x].mi[0],T[x].mx[0]))if(in(y1,y2,T[x].mi[1],T[x].mx[1]))return T[x].sum;if(out(x1,x2,T[x].mi[0],T[x].mx[0]))return 0;if(out(y1,y2,T[x].mi[1],T[x].mx[1]))return 0;if(in(x1,x2,T[x].p[0],T[x].p[0]))if(in(y1,y2,T[x].p[1],T[x].p[1]))ans+=T[x].val;return ans+query(ch[x][0],x1,y1,x2,y2)+query(ch[x][1],x1,y1,x2,y2);}}kd;int main()
{freopen(FILE".in","r",stdin);freopen(FILE".out","w",stdout);n=gi();while(1){int type=gi();if(type==3)break;int x1=gi()^lst,y1=gi()^lst;if(type==1){int A=gi()^lst;kd.nd.p[0]=x1;kd.nd.p[1]=y1;kd.nd.sum=kd.nd.val=A;kd.insert(kd.root,0);if(kd.cnt==sz){for(int i=1;i<=sz;++i)a[i]=kd.T[i];kd.rebuild(kd.root,1,sz,0);sz+=10000;}}if(type==2){int x2=gi()^lst,y2=gi()^lst;lst=kd.query(kd.root,x1,y1,x2,y2);printf("%d\n",lst);}}fclose(stdin);fclose(stdout);return 0;
}
简单题

 

 

 

转载于:https://www.cnblogs.com/fenghaoran/p/8176236.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/453763.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度学习之 RPN(RegionProposal Network)- 区域候选网络

anchor boxes基本概念与作用: feature map 上的一个点可以映射回输入图片上的一个点&#xff0c;以特征图上这个点为中心&#xff0c;预先人为设定 k 个 boxes&#xff0c;这些 boxes 就称为在这个点上生成的 k 个 anchor boxes&#xff08;所有anchor boxes的中心点坐标是一样…

workbench拓扑优化教程_优化技术在水泵水力设计的应用(上篇)

文章来源&#xff1a;安世亚太官方订阅号&#xff08;搜索&#xff1a;Peraglobal&#xff09;CFD技术在泵的内流数值模拟、研究泵内部流动规律和结构方面已广泛应用&#xff0c;取得了很多成果。但是初步设计的产品如果通过CFD仿真得到的性能曲线不能满足使用要求&#xff0c;…

深度学习之 TensorRT

1 简介 TensorRT是一个高性能的深度学习推理&#xff08;Inference&#xff09;优化器&#xff0c;可以为深度学习应用提供低延迟、高吞吐率的部署推理。TensorRT可用于对超大规模数据中心、嵌入式平台或自动驾驶平台进行推理加速。TensorRT现已能支持TensorFlow、Caffe、Mxne…

进制转换中dbho是什么意思_什么是网段?二进制十进制如何互相转换?看完这篇,你就全明白了...

之前的文章讲了ip&#xff0c;子网掩码&#xff0c;网关的关系&#xff0c;今天着重讲一下网段。我们用傻瓜交换机通讯时&#xff0c;一个网段的设备才能互相通讯&#xff0c;怎么能判断两个ip是同一个网段呢&#xff1f;今天就简单的说一下。(这篇文章用语音听可以起到催眠作用…

软件需求与问题解决

&#xff08;一&#xff09; 小满当上项目经理后不久&#xff0c;参与了一个大项目。当时市场签下来的时候&#xff0c;公司里面是欢天喜地的。项目做了一年多。到了交付的时候&#xff0c;用户却很不满意&#xff0c;当初说好的东西&#xff0c;好多都变了卦。用户是上帝&…

flex 换主轴后子元素占满_Chrome72 嵌套 flex 布局修改,你的网站可能会发生布局错乱...

起源2019 年 1 月 29 日&#xff0c;Chrome72 正式版(72.0.3626.81)发布&#xff0c;本次发布带来了一个改变&#xff0c;且没有在更新日志中提及&#xff0c;该改变导致某些网站发生了布局错乱。该改变主要针对的是嵌套的flex布局&#xff0c;下面我们一起看下是怎么回事。问题…

路由到另外一个页面_Nextjs使用解读一(项目搭建与路由系统)

文章说明&#xff1a;1. 之前想搭建个人博客&#xff0c;由于学习的是react技术栈&#xff0c;所以就到处搜罗资料学了nextjs&#xff0c;配合koa就把博客搭起来了。该系列文章基于我的学习笔记&#xff0c;重新整理了一遍&#xff0c;如果有错误之处&#xff0c;还请指正。2. …

微信获取token -1000

最终翻看微信开发api找到需要去配置IP白名单。只需要配置访问来源IP即可。 转载于:https://www.cnblogs.com/yangjinqiang/p/8184663.html

产品技术和管理

为啥纯粹为消费者传递体验的活动可以价格不菲&#xff0c;几为暴利&#xff1f;——谈客户体验作为客户价值提升之源 不论产品还是服务&#xff0c;如果能够为消费者传递有益的体验&#xff0c;其价值就可以在一般的产品服务之上得以体现&#xff1b;附加了体验的产品&#xff…

jquery ztree 设置勾选_047 JAVA-jQuery

jQuery操作元素属性的值表单:<body><input type"button" name"" id"but1" value"测试获得属性值" /><hr />账号&#xff1a;<input type"text" name"sxtzh" id"zhanghao" value&q…

Opencv undefined reference to `cv::imread() Ubuntu编译

Ubuntu下编译一个C文件&#xff0c;C源程序中使用了opencv&#xff0c;opencv的安装没有问题&#xff0c;但是在编译的过程中出现如下错误&#xff1a; undefined reference to cv::imread(std::string const&, int)undefined reference to cv::noArray()undefined referen…

深度学习目标检测之 YOLO v1

这是继 RCNN&#xff0c;fast-RCNN 和 faster-RCNN 之后&#xff0c;rbg&#xff08;RossGirshick&#xff09;针对DL目标检测速度问题提出的另外一种框架。YOLO V1 增强版本GPU中能跑45fps&#xff0c;简化版本155fps。 论文名&#xff1a; 《You Only Look Once&#xff1a;…

编程珠玑番外篇

1.Plan 9 的八卦 在 Windows 下喜欢用 FTP 的同学抱怨 Linux 下面没有如 LeapFTP 那样的方便的工具. 在苹果下面用惯了 Cyberduck 的同学可能也会抱怨 Linux 下面使用 FTP 和 SFTP 是一件麻烦的事情. 其实一点都不麻烦, 因为在 LINUX 系统上压根就不需要用 FTP. 为什么呢? 因…

BT下载原理分析

版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 BitTorrent协议。 BT全名为BitTorrent,是一个p2p软件,你在下载download的同时&#xff0c;也在为其他用户提供上传upload&#xff0c;因为大家是“互相帮助”&#xff0c;所以不会随着用户数的增加而…

表格列求和_excel表格制作,Excel表格的基本操作,包含制作一个表格10方面的知识...

创建表格&#xff0c;插入与删除一行一列或多行多行&#xff0c;一次移动一行一列或多行多列&#xff0c;拆分与合并单元格&#xff0c;单元格内换行&#xff0c;表格求和与求平均值是Excel表格的基本操作&#xff1b;除此之外&#xff0c;Excel表格的基本操作还包括调整行高列…

深度学习之 FPN (Feature Pyramid Networks)

论文题目&#xff1a;Feature Pyramid Networks for Object Detection论文链接&#xff1a;https://arxiv.org/abs/1612.03144论文代码&#xff1a;Caffe版本 https://github.com/unsky/FPN 《Feature Pyramid Networks for Object Detection》这篇论文主要解决的问题是目标检…

ISLR—第二章 Statistical Learning

Statistical Learning Y 和X的关系why estimate f 用来预测 预测的时候可以将f^当成一个black box来用&#xff0c;目的主要是预测对应x时候的y而不关系它们之间的关系。用来推断 推断的时候&#xff0c;f^不能是一个black box&#xff0c;因为我们想知道predictor和response之…

提高编程思想

虚函数和抽象函数有什么区别 虚函数是有代码的并明确允许子类去覆盖&#xff0c;但子类也可不覆盖,就是说可以直接用&#xff0c;不用重写 抽象函数是没有代码&#xff0c;子类继承后一定要重写 ****************************************************************** 在一…

Windows数据库编程接口简介

数据库是计算机中一种专门管理数据资源的系统&#xff0c;目前几乎所有软件都需要与数据库打交道&#xff08;包括操作系统&#xff0c;比如Windows上的注册表其实也是一种数据库&#xff09;&#xff0c;有些软件更是以数据库为核心因此掌握数据库系统的使用方法以及数据库系统…

2019如何转换2010_9102年,你还不知道PPT怎么转换成视频吗?小心落伍了

你在刷抖音的时候有没有刷过这类视频&#xff1a;成为人生赢家必备的书单、5个让你看透人性的电影、6个让你升职加薪的APP...如果你细心观察的话&#xff0c;会发现这类视频的做法基本都是一个样的&#xff0c;像在翻相册一样&#xff0c;一页页过去&#xff0c;所以它们也叫做…