ybt金牌导航4-2-1luogu P4169[Violet]天使玩偶 / SJY摆棋子(K-D tree 模板)
Posted あおいSakura
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ybt金牌导航4-2-1luogu 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;
}
以上是关于ybt金牌导航4-2-1luogu P4169[Violet]天使玩偶 / SJY摆棋子(K-D tree 模板)的主要内容,如果未能解决你的问题,请参考以下文章
ybt金牌导航4-6-6luogu P2617动态排名 / Dynamic Rankings