【ybt金牌导航4-2-1】【luogu P4169】[Violet]天使玩偶 / SJY摆棋子(K-D tree 模板)

[Violet]天使玩偶 / SJY摆棋子

题目链接:ybt金牌导航4-2-1 / luogu P4169

题目大意

初始有一些点,分布在二维平面上。
然后要你进行一些操作:再往上面放一个点,或者给你一个坐标,找到距离它最近的点到它的距离。
(这里的距离是哈密顿距离,就 x,y 坐标差的和)

思路

这道题我们用 K-D tree 来做。
(它其实可以说是 K-D tree 的模板题?)

那就来略讲一下 K-D tree。
K-D tree 就是一个可以存 k 维的点一个二叉树。
然后你这个 K-D tree 就相当于对这些点构成的 k 维空间的一个划分,树中每个点代表一块空间。
而这道题中我们用的是二维 K-D tree,一般用到的也是这个。

它大概是这样,每个深度有一个划分的维度 d,然后在这个深度的点中,它的左儿子的 d 维信息小于它的,它右儿子的 d 维信息大于它的。
那很容易看出你划分的维度当然是轮流来的,就比如先第一维,接着第二维,然后第三维,再变成第一维,以此类推。
(不过说一开始轮的顺序也可以按方差从大到小排,可以缩小时间)

然后划分的依据,容易想到我们要让树尽可能平衡,那我们构树的时候就选中位数作为代表的点,然后再把其他点分到左右两边。(可以用 nth-element)
然后容易看出它是可以不断新放别的点的,就按照每个点,从根节点一直往下走,走到没有点的地方就放在那里。

但是你会想到放多了它可能就不平衡了,那就用替罪羊的思想,也搞个失衡的值。
如果两个子树有一个的个数超过它的个数乘上这个值,就把这个树拍扁重构。

那搞到这一题大概就是你每个点代表一个矩形,而且每个点在树上都有一个代表的点。
然后对于这个节点代表的点,我们求出它到询问给出点的距离。
然后再搞出它两个儿子代表的矩阵到询问点的最短距离。
那这个距离就是这个矩阵里面的点到这个询问点距离的下界,那如果这个下界都比你当前的答案大,那就没有必要跑这个矩阵里面的点了。
(算点到矩阵距离大概就是看四条边,点到四条边的距离)

然后还有一个优化就是,我们可以先跑下界小的矩阵,再跑下界大的。
因为这样答案会更新的更小一点,就可以跳过更多的矩阵。

然后大概这样就好了。
(主要是实现烦)

代码

#include<cstdio> #include<iostream> #include<algorithm> #define INF 1e9 #define alph (0.7)//这个是判断不平衡的(类似替罪羊) #define rr register using namespace std; struct zb { int w[2]; }a[1000001], tmp; struct node { zb a; int l, r, ma[2], mi[2], size; }tree[1000001]; int n, m, op, root, tot, ans; int rebuild[1000001], WD; int read() { int re = 0; char c = getchar(); while (c < '0' || c > '9') c = getchar(); while (c >= '0' && c <= '9') { re = (re << 3) + (re << 1) + c - '0'; c = getchar(); } return re; } void write(rr int now) { if (now > 9) write(now / 10); putchar(now % 10 + '0'); } int operator <(zb x, zb y) {//这个是用来作为 nth-element 函数比较的依据的 return x.w[WD] < y.w[WD]; } int get_point() {//开一个新的点 if (rebuild[0]) return rebuild[rebuild[0]--];//如果是重构就把重构的点拿出来 return ++tot; } int abss(rr int now) { if (now < 0) return -now; return now; } int up(rr int now) {//上传值:坐标最大,最小(其实就是哪个矩阵的位置),里面点的个数 for (int i = 0; i < 2; i++) { tree[now].mi[i] = tree[now].a.w[i]; tree[now].ma[i] = tree[now].a.w[i]; if (tree[now].l) { tree[now].mi[i] = min(tree[now].mi[i], tree[tree[now].l].mi[i]); tree[now].ma[i] = max(tree[now].ma[i], tree[tree[now].l].ma[i]); } if (tree[now].r) { tree[now].mi[i] = min(tree[now].mi[i], tree[tree[now].r].mi[i]); tree[now].ma[i] = max(tree[now].ma[i], tree[tree[now].r].ma[i]); } } tree[now].size = tree[tree[now].l].size + tree[tree[now].r].size + 1; } int build(rr int l, rr int r, rr int wd) { if (l > r) return 0; rr int now = get_point(); rr int mid = (l + r) >> 1; WD = wd; nth_element(a + l, a + mid, a + r + 1); tree[now].a = a[mid];//把中间那个搞出来 tree[now].l = build(l, mid - 1, wd ^ 1); tree[now].r = build(mid + 1, r, wd ^ 1); up(now); return now; } void make_again(rr int root, rr int num) {//拍扁 if (tree[root].l) make_again(tree[root].l, num); a[num + tree[tree[root].l].size + 1] = tree[root].a; rebuild[++rebuild[0]] = root; if (tree[root].r) make_again(tree[root].r, num + tree[tree[root].l].size + 1); } void check(rr int &root, rr int wd) { if (alph * tree[root].size < tree[tree[root].l].size || alph * tree[root].size < tree[tree[root].r].size) {//不平衡了 make_again(root, 0);//拍扁 root = build(1, tree[root].size, wd);//重构 } } void insert(rr zb now, rr int &root, rr int wd) {//插入点 if (!root) {//已经找到位置了 root = get_point(); tree[root].a = now; tree[root].l = tree[root].r = 0; up(root); return ; } if (tree[root].a.w[wd] < now.w[wd])//判断它应该放到哪边 insert(now, tree[root].r, wd ^ 1); else insert(now, tree[root].l, wd ^ 1); up(root); check(root, wd); } int get_dis(rr zb x, rr zb y) {//求哈密顿距离 return abss(x.w[0] - y.w[0]) + abss(x.w[1] - y.w[1]); } int blog_dis(rr zb x, rr int now) {//求一个点到一个矩阵的哈密顿距离 rr int re = 0; for (int i = 0; i < 2; i++) { re += max(0, tree[now].mi[i] - x.w[i]);//分别从这个矩阵这个维度的两个(边) re += max(0, x.w[i] - tree[now].ma[i]); } return re; } void get_close(rr zb now, rr int root) {//找到距离这个点最近的点 ans = min(ans, get_dis(now, tree[root].a));//先跟中位数代表的点算距离 rr int ldis = INF, rdis = INF; if (tree[root].l) ldis = blog_dis(now, tree[root].l); if (tree[root].r) rdis = blog_dis(now, tree[root].r); if (ldis < rdis) {//先搜答案下界小的(搜之前还要判断是否可能比答案小) if (ldis < ans) get_close(now, tree[root].l); if (rdis < ans) get_close(now, tree[root].r); } else { if (rdis < ans) get_close(now, tree[root].r); if (ldis < ans) get_close(now, tree[root].l); } } int main() { // freopen("read.txt", "r", stdin); // freopen("write.txt", "w", stdout); n = read(); m = read(); for (rr int i = 1; i <= n; i++) { a[i].w[0] = read(); a[i].w[1] = read(); } root = build(1, n, 0); while (m--) { op = read(); tmp.w[0] = read(); tmp.w[1] = read(); if (op == 1) { insert(tmp, root, 0); } else { ans = INF; get_close(tmp, root); write(ans); putchar('\n'); } } return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/YBT_JPDH_4-2-1.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(44)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示