KDT 从入门到夺门而出

news/2025/1/31 21:34:55/文章来源:https://www.cnblogs.com/biyimouse/p/18695982

简介

首先要知道 \(KD-Tree\) 是干什么的,它最广泛的用法便是维护 \(k\) 维最近点对(大部分时候是二维)。

先来讲没有插入,直接建树的。

它的每个结点维护这样子的数据,其中 \(lc\)\(rc\) 代表左右儿子,\(v[i]\) 代表第 \(i\) 维当前点的取值,\(L[i]\)\(U[i]\) 分别代表第 \(i\) 维上当前点对应的子树中所有点的范围,它其实对应了一个矩形

比如一棵子树中根为 \((114514,114514)\),左儿子为 \((1919, 1919)\) 右儿子为 \((810,810)\),那么对于根来说 \(L[1] = 810, R[1] = 114514\)

struct KD {int lc, rc;double v[2], L[2], U[2];bool operator < (const KD &t) const {return v[K] < t.v[K];}
} tr[N]; 

然后是建树过程,其实和替罪羊树的重构过程很像,都是拍扁再拎起来。具体地我们使用 nth_element 函数,将一排数根据 \(mid\) 分成两半。同时,为了保证复杂度,我们要轮流对第 \(k\) 维排序,即 \(0,1\) 循环,上文的比较函数也是为了这个所定义的。值得注意的是 \(k\) 是函数内的 \(K\) 是一个全局变量。

pushup 操作也比较简朴,看看就懂了,就是用儿子更新父亲。

void pushup(int u) {rep(i, 0, 1) {tr[u].L[i] = tr[u].U[i] = tr[u].v[i];if (tr[u].lc) {tr[u].L[i] = min(tr[u].L[i], tr[tr[u].lc].L[i]);tr[u].U[i] = max(tr[u].U[i], tr[tr[u].lc].U[i]);} if (tr[u].rc) {tr[u].L[i] = min(tr[u].L[i], tr[tr[u].rc].L[i]);tr[u].U[i] = max(tr[u].U[i], tr[tr[u].rc].U[i]);}}
}
int build(int l, int r, int k) {if (l > r) return 0;int mid = l + r >> 1;K = k; nth_element(tr + l, tr + mid, tr + r);tr[mid].lc = build(l, mid - 1, k ^ 1);tr[mid].rc = build(mid + 1, r, k ^ 1);pushup(mid);return mid;
}

查询也不算非常困难,我们从根开始,分别计算要查的结点 \(cur\) 到根的距离,以及到左右儿子所在范围的最近距离。由于上文的 \(L\)\(U\) 在两维状态下可以看做是一个矩形,所以相当于一个点到矩形的最短距离。可以看看代码画图理解一下。

回到 query 中,我们得知 \(dist\) 后,可以贪心地在左右儿子中选择 \(dist\) 小的来优先更新,然后在考虑另一侧。同时左右边的最优答案一定得小于当前全局最优值,否则不用更新。

inline double sq(double x) {return x * x;
}
inline double dis1(int x) {double res = 0;rep(i, 0, 1) res += sq(tr[cur].v[i] - tr[x].v[i]);return res;
}
inline double dis2(int x) {if (!x) return 2e18;double res = 0;rep(i, 0, 1) res += sq(max(0.0, tr[cur].v[i] - tr[x].U[i])) + sq(max(0.0, tr[x].L[i] - tr[cur].v[i]));return res;
}
void query(int u) {if (!u) return;if (u != cur) ans = min(ans, dis1(u));double d1 = dis2(tr[u].lc), d2 = dis2(tr[u].rc);if (d1 < d2) {if (d1 < ans) query(tr[u].lc);if (d2 < ans) query(tr[u].rc);} else {if (d2 < ans) query(tr[u].rc);if (d1 < ans) query(tr[u].lc);}
}

经过证明(我不会),在处理二维时的复杂度是根号的,\(build\)\(O(nlogn)\)\(k\) 维是 \(O(n^{1 - \frac{1}{k}})\) 的。

当然你也可以动态差点不 \(build\),同样类似于替罪羊数当 \(A * sz[root] \geq max(sz[lc], sz[rc])\) 时就直接重构。\(A\) 我一般取 \(0.7\)

bool cmp(int a, int b) {return tr[a].v[K] < tr[b].v[K];
}
int rebuild(int l, int r, int k) {if (l > r) return 0;int mid = l + r >> 1;K = k; nth_element(g + l, g + mid, g + r + 1, cmp);tr[g[mid]].lc = rebuild(l, mid - 1, k ^ 1);tr[g[mid]].rc = rebuild(mid + 1, r, k ^ 1);pushup(g[mid]);return g[mid];
}
void dfs(int u) {if (!u) return;g[++ cnt] = u;dfs(tr[u].lc);dfs(tr[u].rc);
}
void check(int &u, int k) {if (tr[u].sz * A < max(tr[tr[u].lc].sz, tr[tr[u].rc].sz)) cnt = 0, dfs(u), u = rebuild(1, cnt, k);
}
void insert(int &u, int k) {if (!u) { u = cur; pushup(u); return; }insert(tr[cur].v[k] <= tr[u].v[k] ? tr[u].lc : tr[u].rc, k ^ 1);pushup(u);check(u, k);
}

模板

以P1429 平面最近点对(加强版)为例,贴个板子。

#include <bits/stdc++.h>
#define rep(i, a, b) for (int i = (a); i <= (b); i ++)
#define fro(i, a, b) for (int i = (a); i >= b; i --)
#define INF 0x3f3f3f3f
#define eps 1e-6
#define lowbit(x) (x & (-x))
#define initrand srand((unsigned)time(0))
#define random(x) ((LL)rand() * rand() % (x))
#define eb emplace_back
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, int> PDI;
inline int read() {int x = 0, f = 1;char ch = getchar();while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }while (ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }return x * f;
}const int N = 200010;
int n, K, cur;
double ans = 2e18;struct KD {int lc, rc;double v[2], L[2], U[2];bool operator < (const KD &t) const {return v[K] < t.v[K];}
} tr[N]; void pushup(int u) {rep(i, 0, 1) {tr[u].L[i] = tr[u].U[i] = tr[u].v[i];if (tr[u].lc) {tr[u].L[i] = min(tr[u].L[i], tr[tr[u].lc].L[i]);tr[u].U[i] = max(tr[u].U[i], tr[tr[u].lc].U[i]);} if (tr[u].rc) {tr[u].L[i] = min(tr[u].L[i], tr[tr[u].rc].L[i]);tr[u].U[i] = max(tr[u].U[i], tr[tr[u].rc].U[i]);}}
}int build(int l, int r, int k) {if (l > r) return 0;int mid = l + r >> 1;K = k; nth_element(tr + l, tr + mid, tr + r + 1);tr[mid].lc = build(l, mid - 1, k ^ 1);tr[mid].rc = build(mid + 1, r, k ^ 1);pushup(mid);return mid;
}inline double sq(double x) {return x * x;
}inline double dis1(int x) {double res = 0;rep(i, 0, 1) res += sq(tr[cur].v[i] - tr[x].v[i]);return res;
}inline double dis2(int x) {if (!x) return 2e18;double res = 0;rep(i, 0, 1) res += sq(max(0.0, tr[cur].v[i] - tr[x].U[i])) + sq(max(0.0, tr[x].L[i] - tr[cur].v[i]));return res;
}void query(int u) {if (!u) return;if (u != cur) ans = min(ans, dis1(u));double d1 = dis2(tr[u].lc), d2 = dis2(tr[u].rc);if (d1 < d2) {if (d1 < ans) query(tr[u].lc);if (d2 < ans) query(tr[u].rc);} else {if (d2 < ans) query(tr[u].rc);if (d1 < ans) query(tr[u].lc);}
}int main() {n = read();rep(i, 1, n) scanf("%lf%lf", &tr[i].v[0], &tr[i].v[1]);int root = build(1, n, 0);for (cur = 1; cur <= n; cur ++) query(root); printf("%.4lf\n", sqrt(ans));return 0;
}
#include <bits/stdc++.h>
#define rep(i, a, b) for (int i = (a); i <= (b); i ++)
#define fro(i, a, b) for (int i = (a); i >= b; i --)
#define INF 0x3f3f3f3f
#define eps 1e-6
#define lowbit(x) (x & (-x))
#define initrand srand((unsigned)time(0))
#define random(x) ((LL)rand() * rand() % (x))
#define eb emplace_back
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, int> PDI;
inline int read() {int x = 0, f = 1;char ch = getchar();while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }while (ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }return x * f;
}const int N = 200010;
const double A = 0.7;
int n, K, root, cur;
double ans = 2e18;int idx;
struct KD {int lc, rc, sz;double v[2], L[2], U[2];bool operator < (const KD &t) const {return v[K] < t.v[K];}
} tr[N]; void pushup(int u) {tr[u].sz = tr[tr[u].lc].sz + tr[tr[u].rc].sz;rep(i, 0, 1) {tr[u].L[i] = tr[u].U[i] = tr[u].v[i];if (tr[u].lc) {tr[u].L[i] = min(tr[u].L[i], tr[tr[u].lc].L[i]);tr[u].U[i] = max(tr[u].U[i], tr[tr[u].lc].U[i]);} if (tr[u].rc) {tr[u].L[i] = min(tr[u].L[i], tr[tr[u].rc].L[i]);tr[u].U[i] = max(tr[u].U[i], tr[tr[u].rc].U[i]);}}
}int g[N], cnt;bool cmp(int a, int b) {return tr[a].v[K] < tr[b].v[K];
}int rebuild(int l, int r, int k) {if (l > r) return 0;int mid = l + r >> 1;K = k; nth_element(g + l, g + mid, g + r + 1, cmp);tr[g[mid]].lc = rebuild(l, mid - 1, k ^ 1);tr[g[mid]].rc = rebuild(mid + 1, r, k ^ 1);pushup(g[mid]);return g[mid];
}void dfs(int u) {if (!u) return;g[++ cnt] = u;dfs(tr[u].lc);dfs(tr[u].rc);
}void check(int &u, int k) {if (tr[u].sz * A < max(tr[tr[u].lc].sz, tr[tr[u].rc].sz)) cnt = 0, dfs(u), u = rebuild(1, cnt, k);
}void insert(int &u, int k) {if (!u) { u = cur; pushup(u); return; }insert(tr[cur].v[k] <= tr[u].v[k] ? tr[u].lc : tr[u].rc, k ^ 1);pushup(u);check(u, k);
}inline double sq(double x) {return x * x;
}inline double dis1(int x) {double res = 0;rep(i, 0, 1) res += sq(tr[cur].v[i] - tr[x].v[i]);return res;
}inline double dis2(int x) {if (!x) return 2e18;double res = 0;rep(i, 0, 1) res += sq(max(0.0, tr[cur].v[i] - tr[x].U[i])) + sq(max(0.0, tr[x].L[i] - tr[cur].v[i]));return res;
}void query(int u) {if (!u) return;if (u != cur) ans = min(ans, dis1(u));double d1 = dis2(tr[u].lc), d2 = dis2(tr[u].rc);if (d1 < d2) {if (d1 < ans) query(tr[u].lc);if (d2 < ans) query(tr[u].rc);} else {if (d2 < ans) query(tr[u].rc);if (d1 < ans) query(tr[u].lc);}
}int main() {n = read();rep(i, 1, n) scanf("%lf%lf", &tr[i].v[0], &tr[i].v[1]);for (cur = 1; cur <= n; cur ++) insert(root, 0);for (cur = 1; cur <= n; cur ++) query(root); printf("%.4lf\n", sqrt(ans));return 0;
}

模板题

P2479 [SDOI2010] 捉迷藏

距离计算更改为曼哈顿距离,然后再增加统计一下最大值即可。

直接建树会比动态插入快非常多。

代码

P4148 简单题

操作 \(1\) 可以看作动态插点,操作 \(2\) 可以直接用类似线段树查询的方式,对于每个结点分类讨论三种(我们将一个 \(KDT\) 结点表示范围看作矩形):

  1. 该节点所对矩形完全不包含于询问矩形
  2. 该节点所对矩形完全包含于询问矩形
  3. 部分包含

对于 \(1\)\(2\) 来说是简单的,对于 \(3\),我们可以判断一下当前节点的 \(v\) 是否在矩形内,然后递归左右子树最后加上根的贡献。
时间复杂度被证明是根号的。

代码

困难一点的题

P5471 [NOI2019] 弹跳

考虑 \(KDT\) 优化建图后跑 \(dijkstra\),我们把 \(1\sim n\) 记为原始的点(下文称为实点),\(n + 1\sim 2n\) 记为 \(KDT\) 建出来的点(虚点)。显然一个实点 \(u\) 对应虚点 \(u + n\)。对于一个虚点 \(u\),它显然可以向 \(u - n\) 连边,也可以向它在 \(KDT\) 中的左右儿子连边;对于一个实点,我们遍历从该点出发的弹跳装置,分类讨论(以下点均为实点所对虚点):

  1. 一个虚点 \(x\) 对应的矩阵完全包含在弹跳装置内,直接将 \(u\)\(x\) 连边
  2. 完全不包含,直接返回
  3. 部分包含的话,如果该虚点的坐标能够包含于弹跳装置内就让 \(u\)\(x - n\) 连边

然而如果真的连边的话会被卡爆,我们考虑边做 \(dij\) 边跑上面过程,每次不建边直接用需要的点进行更新操作。

于是这道题就做完了,代码稍微有点难写。

代码

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

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

相关文章

mybatis的demo程序

事实上,关于mybatis框架的所有过程,均可见于 https://mybatis.net.cn/getting-started.html 一 创建数据库 该部分就是简单的sql语句创建数据库和数据表的过程,不多赘婿,结果如图所示 二 创建maven项目,导入依赖 首先创建maven项目,注意将maven的地址改为自己的maven,而…

lightroom调色笔记

1.关于色彩关系任意两种颜色的混合都会是另一种颜色的互补色 RGB模式下: 红绿混合是黄,黄是蓝色的互补色 蓝绿混合是青色,红色是青色互补色 红蓝混合是品色,品色是绿色的互补色 2.亮度的区域划分3.关于曲线上的锚点 使用ctrl在画面上可以在曲线上找到需要调整的位置 画面偏…

05. 用户组管理

一、什么是用户组管理每一个用户都有一个用户组,系统可以对一个用户组的所有用户进行集中管理。不同 Linux 系统对用户组的规定有所不同。Linux 下的用户属于与它同名的用户组,这个用户组在创建用户时同步创建。用户组的管理涉及用户组的添加、删除和修改。组的增加、删除和修…

华为mate70pro+ 对比 小米10ultra 高像素模式

华为 拍摄一张大概要5-7秒 小米 只需不到1秒 华为明显要强华为小米 华为小米

Qt Quick与ROS通过UDP协议实现网络通信

实现目标 项目需要编写一个无人机地面站,无人机在ROS系统下运行,地面站需要与无人机建立通信,能够控制无人机起飞、降落、飞行,并能够接收无人机的状态信息。 该无人机系统的组成如下图所示:地面站通过无线网络与无人机上位机建立通信,上位机负责将飞控的无人机状态数据转…

5.C++提高编程

C++提高编程。C++提高编程 本阶段主要针对C++泛型编程和STL技术做详细讲解,探讨C++更深层的使用 1 模板 1.1 模板的概念 模板就是建立通用的模具,大大提高复用性 例如生活中的模板 一寸照片模板:PPT模板:模板的特点: 模板不可以直接使用,它只是一个框架 模板的通用并不是…

又来新活了!AI电商搜索,或是下一个90亿美元独角兽?

全新体验,大模型驱动的对话式购物搜索。图源:https://www.shopencore.ai/ 全新体验,大模型驱动的对话式购物搜索。 Encore, 由2024年10月成立的美国初创公司开发。定位于二手商品对话式购物搜索,最终目标为个人购物助理。 2024年12月3日获得YC(Y combinator)的50万美元天使…

qq网页版下载音乐教程

点一首音乐开始播放,务必要播放界面内只有一首音乐,然后f12调试,找到audio标签;然后复制src=”” 双引号内的内容到新标签打开,然后在播放栏,右键,就可以保存音乐了,注意有的音乐是m4a格式,下载完成后还要转换成mp3。谢雨尘安-谢雨尘安的博客

gin: 校验参数时返回自定义错误信息

一,代码 1,global/validator.go package globalimport "github.com/go-playground/validator/v10"//存放GetMessages()方法 type Validator interface {GetMessages() ValidatorMessages }//校验信息 type ValidatorMessages map[string]string// GetErrorMsg方法,…

VM笔记_Modbus通信触发流程

1,通信触发流程 ①通信配置② 接收事件新建③全局触发-事件触发4, 通信心跳配置和启用5, 效果展示

[SWPUCTF 2021 新生赛]easyupload3.0 Writeup

题目来源:NSSCTF 题目方向:Web 题目类型:文件上传 2.0的做法和1.0相同,不过用.phtml绕过就行 1.这里去了解了一下.htaccess文件: htaccess文件是Apache服务中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮助我们实现:网页301重定向、自定义404错…

数据库性能调优中的配置参数调整:提升系统效率的关键环节

title: 数据库性能调优中的配置参数调整:提升系统效率的关键环节 date: 2025/1/31 updated: 2025/1/31 author: cmdragon excerpt: 数据库的性能直接影响到应用程序的响应能力和用户体验,因此在日常运维中,管理员需要定期对数据库系统进行性能调优。配置参数调整是数据库性…