KD-Tree这玩意还真的是有趣啊....

(基本完全不理解)

只能谈一点自己的对KD-Tree的了解了。

首先这个玩意就是个暴力...

他的结构有点类似二叉搜索树

每一层都是以一个维度作为划分标准。

我们对于当前层,选择所有剩余点中,该维度比较中间的那个点作为基准点,比他小(这一维度)就放到左子树,不然右子树。

然后维度的选取是交替选取

就比如说,我们第一层是按照第一个维度,第二层是第二维度,第三层是第一维度....以此类推,直到没有剩余的点为止。

那么构造的时候,由于我们涉及到要把大于某个数的数移动到这个数后面这个操作,这时候就需要一个黑科技!!!!

\[nth_element(a+l,a+x,a+r+1)
\]

这个操作表示我们将\([l,r]\)区间第\(x\)大移动到第\(x\)个位置,然后大于他的放到后面,小于的放到前面

那么这时候其实就比较好解决\(build\)的问题了

  1. void build(int &x,int l,int r,int dd) //建树的时候,我们对于不同的维度,我们选择交替建树,每次选择这个维度里面,值最中间的点作为基准点。
  2. {
  3. ymh = dd;
  4. int mid = l+r >> 1;
  5. x = mid;
  6. nth_element(t+l,t+x,t+r+1); //将第k大的放到第k个位置,然后比他大的都在前面,小的都在后面
  7. for (int i=0;i<=1;i++)
  8. t[x].mx[i]=t[x].mn[i]=t[x].d[i];
  9. if (l<x) build(t[x].l,l,mid-1,dd^1); //递归处理
  10. if (r>x) build(t[x].r,mid+1,r,dd^1);
  11. up(x);
  12. }

那么现在我们该怎么解决\(query\)的问题呢

首先,KD-Tree的询问是不一定复杂度的(可能很高),但是据说是\(\sqrt n\)的?我也不是很确定的

首先,我们要写一个估价函数:

其实这个玩意,就是用来判断这个子树有没有更新答案的可能性

那么既然我们只需要判断可能性,可以直接用一些理论上的东西(比如说子树某一维度的\(mn或者mx\))

也就说,我们将每个维度和当前询问点差距最大的那个距离加起来,看看是否合法

  1. int calc(int x) //这里这个函数的作用是个估价函数
  2. //如果在一颗子树中,计算理论上的距离当前询问点的最远距离(因为用mn和mx算,所以是理论上)
  3. {
  4. if(!x) return -100;
  5. int ans=0;
  6. for (int i=0;i<=1;i++)
  7. ans+=max((t[x].mx[i]-now.d[i])*(t[x].mx[i]-now.d[i]),(t[x].mn[i]-now.d[i])*(t[x].mn[i]-now.d[i]));
  8. return ans;
  9. }

query的时候,我们每次只需要用当前点更新答案,然后看看需要不需要进入子树里面计算,但是这里有个小\(tips\),就是

我们进入子树的时候,要选择那个可能性更大子树进入(因为存在进入完更优秀的那个子树之后,另一个子树就没有必要再进去了)

  1. void query(int x)
  2. {
  3. if (!x) return;
  4. int dl = calc(t[x].l);
  5. int dr = calc(t[x].r);
  6. int d = getdis(t[x],now);
  7. if (d>q.top().dis || (d==q.top().dis && t[x].num<q.top().num)) //这里用堆维护出最大的k个距离
  8. {
  9. q.pop();
  10. q.push((Node){d,t[x].num});
  11. }
  12. if (dl>dr) //之所以要分这个顺序,是因为有可能更新完大的那个子树,就没有必要进入小的子树
  13. {
  14. if (dl>=q.top().dis) query(t[x].l); //进入的条件是你的理论距离是大于当前堆顶的(也就是有可能会入堆)
  15. if (dr>=q.top().dis) query(t[x].r);
  16. }
  17. else
  18. {
  19. if (dr>=q.top().dis) query(t[x].r);
  20. if (dl>=q.top().dis) query(t[x].l);
  21. }
  22. }

那么基本上所有\(KD-Tree\)题的套路部分就这么多了。。

剩下的就是因题而异了

回到这个题。

他要求的是第k大的点对。

那我们这时候就要用一个\(priority_queue\)来维护一个k个值的堆

每次对于一个新的值,我们看是否比当前堆中最小的大,如果大就弹出原来的\(top\),然后\(push\)新的这个

然后求出来距离尽量大的k个点,最后直接输出\(q.top()\)

不过还是有很多要注意的地方

直接看代码吧

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<algorithm>
  4. #include<cstring>
  5. #include<cmath>
  6. #include<queue>
  7. #include<map>
  8. #include<set>
  9. #define mk makr_pair
  10. #define ll long long
  11. #define int long long
  12. using namespace std;
  13. inline int read()
  14. {
  15. int x=0,f=1;char ch=getchar();
  16. while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
  17. while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  18. return x*f;
  19. }
  20. const int maxn = 3e5+1e2;
  21. const int inf = 1e18;
  22. struct KD{
  23. int d[2],mx[2]; //d表示每一维的数是多少,mn表示子树mn,mx表示子树mx
  24. int mn[2];
  25. int l,r,num;
  26. };
  27. KD t[maxn],now;
  28. int n,m,root,ymh;
  29. bool operator< (KD a,KD b)
  30. {
  31. return a.d[ymh]<b.d[ymh]; //便于之后的nth_element的计算
  32. }
  33. void up(int root) //更新信息
  34. {
  35. for (int i=0;i<=1;i++)
  36. {
  37. t[root].mn[i]=min(t[root].mn[i],min(t[t[root].l].mn[i],t[t[root].r].mn[i]));
  38. t[root].mx[i]=max(t[root].mx[i],max(t[t[root].l].mx[i],t[t[root].r].mx[i]));
  39. }
  40. }
  41. void build(int &x,int l,int r,int dd) //建树的时候,我们对于不同的维度,我们选择交替建树,每次选择这个维度里面,值最中间的点作为基准点。
  42. {
  43. ymh = dd;
  44. int mid = l+r >> 1;
  45. x = mid;
  46. nth_element(t+l,t+x,t+r+1); //将第k大的放到第k个位置,然后比他大的都在前面,小的都在后面
  47. for (int i=0;i<=1;i++)
  48. t[x].mx[i]=t[x].mn[i]=t[x].d[i];
  49. if (l<x) build(t[x].l,l,mid-1,dd^1); //递归处理
  50. if (r>x) build(t[x].r,mid+1,r,dd^1);
  51. up(x);
  52. }
  53. //上面是KD_Tree问题(
  54. struct Node{
  55. int dis,num;
  56. };
  57. bool operator < (Node a,Node b)
  58. {
  59. return a.dis>b.dis || ((a.dis==b.dis)&&(a.num<b.num));
  60. }
  61. priority_queue<Node> q;
  62. int getdis(KD a,KD b) //计算两个点之间的距离
  63. {
  64. return (a.d[0]-b.d[0])*(a.d[0]-b.d[0])+(a.d[1]-b.d[1])*(a.d[1]-b.d[1]);
  65. }
  66. int calc(int x) //这里这个函数的作用是个估价函数
  67. //如果在一颗子树中,计算理论上的距离当前询问点的最远距离(因为用mn和mx算,所以是理论上)
  68. {
  69. if(!x) return -100;
  70. int ans=0;
  71. for (int i=0;i<=1;i++)
  72. ans+=max((t[x].mx[i]-now.d[i])*(t[x].mx[i]-now.d[i]),(t[x].mn[i]-now.d[i])*(t[x].mn[i]-now.d[i]));
  73. return ans;
  74. }
  75. void query(int x)
  76. {
  77. if (!x) return;
  78. int dl = calc(t[x].l);
  79. int dr = calc(t[x].r);
  80. int d = getdis(t[x],now);
  81. if (d>q.top().dis || (d==q.top().dis && t[x].num<q.top().num)) //这里用堆维护出最大的k个距离
  82. {
  83. q.pop();
  84. q.push((Node){d,t[x].num});
  85. }
  86. if (dl>dr) //之所以要分这个顺序,是因为有可能更新完大的那个子树,就没有必要进入小的子树
  87. {
  88. if (dl>=q.top().dis) query(t[x].l); //进入的条件是你的理论距离是大于当前堆顶的(也就是有可能会入堆)
  89. if (dr>=q.top().dis) query(t[x].r);
  90. }
  91. else
  92. {
  93. if (dr>=q.top().dis) query(t[x].r);
  94. if (dl>=q.top().dis) query(t[x].l);
  95. }
  96. }
  97. signed main()
  98. {
  99. t[0].mn[0]=t[0].mn[1]=inf;
  100. t[0].mx[0]=t[0].mx[1]=-inf;
  101. n=read();
  102. for (int i=1;i<=n;i++)
  103. {
  104. t[i].d[0]=read();
  105. t[i].d[1]=read();
  106. t[i].num=i;
  107. }
  108. build(root,1,n,1);
  109. m=read();
  110. for (int i=1;i<=m;i++)
  111. {
  112. while (!q.empty()) q.pop();
  113. now.d[0]=read();
  114. now.d[1]=read();
  115. int k=read();
  116. for (int i=1;i<=k;i++)
  117. q.push((Node){-1,-1}); //事先插入k个极小值
  118. query(root);
  119. cout<<q.top().num<<"\n";
  120. }
  121. return 0;
  122. }
  123. //https://blog.csdn.net/ws_yzy/article/details/50790735

洛谷2093 JZPFAR + KD-Tree学习笔记 (KD-Tree)的更多相关文章

  1. 解题:洛谷2093 JZPFAR

    题面 初见K-D Tree 其实这样的题(欧几里得距离第$x$近点对)不应该用K-D Tree做,因为会被构造数据卡成$O(n^2)$,随机的另说. 但是并没有找到合适的K-D Tree的题(区域统计 ...

  2. dsu on tree学习笔记

    前言 一次模拟赛的\(T3\):传送门 只会\(O(n^2)\)的我就\(gg\)了,并且对于题解提供的\(\text{dsu on tree}\)的做法一脸懵逼. 看网上的其他大佬写的笔记,我自己画 ...

  3. 珂朵莉树(Chtholly Tree)学习笔记

    珂朵莉树(Chtholly Tree)学习笔记 珂朵莉树原理 其原理在于运用一颗树(set,treap,splay......)其中要求所有元素有序,并且支持基本的操作(删除,添加,查找......) ...

  4. splay tree 学习笔记

    首先感谢litble的精彩讲解,原文博客: litble的小天地 在学完二叉平衡树后,发现这是只是一个不稳定的垃圾玩意,真正实用的应有Treap.AVL.Splay这样的查找树.于是最近刚学了学了点S ...

  5. 洛谷 P2056 [ZJOI2007]捉迷藏 || bzoj 1095: [ZJOI2007]Hide 捉迷藏 || 洛谷 P4115 Qtree4 || SP2666 QTREE4 - Query on a tree IV

    意识到一点:在进行点分治时,每一个点都会作为某一级重心出现,且任意一点只作为重心恰好一次.因此原树上任意一个节点都会出现在点分树上,且是恰好一次 https://www.cnblogs.com/zzq ...

  6. Extjs学习笔记--Ext.tree.Panel

    Ext.create('Ext.tree.Panel', { title: 'Simple Tree', width: 200, height: 150, store: store, rootVisi ...

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

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

  8. k-d tree 学习笔记

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

  9. K-D Tree学习笔记

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

随机推荐

  1. 小程序跨页面传递data数据的三种方法

    Q:小程序怎么把页面data里的数据传到另外的页面? 或者小程序怎么吧表单里的数据传到另外的页面?A:1.可以使用url传递数据. 例如在A页面中传递数据,需要注意的是,wx.switchTab中的u ...

  2. Leaflet 操作OSM(OpenStreetMap) 设置地图style

    Leaflet是一个开源的地图操作库,其中mapbox是其一个插件,这个插件可以自定义想要的地图格式.https://www.mapbox.com/mapbox-gl-js/api/这是其官网地址. ...

  3. 基于源码编译的lnmp架构实现论坛的搭建及memcache的应用

    系统环境: RHEL6 x86-64 selinux and iptables disabled LNMP代表的就是:Linux系统下Nginx+MySQL+PHP这种网站服务器架构 Linux是一类 ...

  4. 剑指 Offer 33. 二叉搜索树的后序遍历序列

    剑指 Offer 33. 二叉搜索树的后序遍历序列 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果.如果是则返回 true,否则返回 false.假设输入的数组的任意两个数字都互不相同. ...

  5. elasticsearch支持大table格式数据的搜索

    一.问题源起 数据情况 TableMeta, 保存table的元数据,通过fileId关联具体的GridFS文件: id name creator fileId 1 table1 mango f1 2 ...

  6. 动态拼接表达式——Expression

    我们在项目中会遇到以下查询需求吗? 比如需要查询出满足以下条件的会员: 条件组一:30-40岁的男性会员 条件组二:20-30岁的女性会员 条件组三:60-80岁性别未知的会员 条件组内是并且关系,但 ...

  7. [第十四篇]——Docker Machine之Spring Cloud直播商城 b2b2c电子商务技术总结

    Docker Machine 简介 Docker Machine 是一种可以让您在虚拟主机上安装 Docker 的工具,并可以使用 docker-machine 命令来管理主机. Docker Mac ...

  8. Vue项目中应用TypeScript

    一.前言 与如何在React项目中应用TypeScript类似 在VUE项目中应用typescript,我们需要引入一个库vue-property-decorator, 其是基于vue-class-c ...

  9. Spring框架(第二天)

    一. 注入 a)  set i. JDK 1.八种基本类型(+包装类)+String <bean id="User" class="com.dsl.test2.Us ...

  10. Feign超时不生效问题

    使用Feign作为RPC调用组件,可以配置连接超时和读取超时两个参数 使用Feign配置超时需要注意:Feign内部使用了负载均衡组件Ribbon,而Ribbon本身也有连接超时和读取超时相关配置一. ...