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金牌导航5-2-3luogu P4292重建计划

ybt金牌导航4-6-6luogu P2617动态排名 / Dynamic Rankings

ybt金牌导航4-2-3luogu P4357K远点对

luogu P4278ybt金牌导航4-5-2带插入区间K小值(树套树做法)

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

ybt最短路径问题