HDU 5992: KD-Tree

题意:给出N个酒店,每个酒店用平面上一个整点表示,每个酒店有一个价格C。现在有若干次询问,每次询问(x,y,c),求出距离(x,y)最近的且价格不超过C的酒店。


题解:这题要是在比赛上,绝对直接按照C排序,然后离线暴力。但是正解也是要学习的,这个题的正解是kd-Tree,就是所谓的多维度二叉搜索树,众所周知,二叉搜索树有很多种,主要都在贯彻一个思想:降低树的高度,提高搜索效率。那么kd-Tree有多维,如果直接按照多元组的排序来构建二叉树的话,对搜索效率没有任何提升(无法剪枝)。


kd-Tree做出的一个改变就是:每次选取k维中的一维来做关键字来划分左右儿子。再结合降低高度的需求,我们可以选取这k维中方差最大的一维来做关键字,这样这一维数据的差别很大,那么左右儿子的差别响应也就比较大,于是就有很大希望可以剪枝。


关于剪枝:将每个点想象成一个k维空间的点,仿照三维空间中,三维的东西叫做体,二位的东西叫做面,一维的东西叫做线,那么在k维空间中,k-1维的东西就是“面”。假设在某个点是由X维来作为关键词,那么就相当于用mid位置的数据做一个“面”,左儿子的所有点都分布在这个“面”的“左边”,右儿子的所有点都分布在“右边”。而假设询问的点分布在右侧,那么我们可以知道,面左侧的点到询问点的理论最小距离是询问点到“面”的“距离”(k维空间的距离),那么我们可以先把这个理论最小距离算出来,然后我们先搜询问点所在的右侧这一个儿子,然后得到了答案之后,如果比这个理论最小距离小,那么左儿子可以直接剪枝,相反,就还是得搜一下左儿子。


由于使用了方差最大的维度来做每一层的关键词,所以说剪枝的可能是很大的,虽然理论上最坏的复杂度还是o(N),但是想要造出卡kd-Tree的数据也是需要下一番狠功夫的。所以一般来讲可以近似的认为kd-Tree的搜索效率为logn级别的。如果说真的遇到了卡kd-Tree的数据。。。。我觉得可以仿照treap那样子每一层加一个随机因子卡一卡就能过掉(口胡)。


Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 2e5+100;
const LL INF = 0x3f3f3f3f3f3f3f3fLL;
int m,n;
const int demension = 2;
struct Hotel{
	int pos[demension],id,c;
}hotel[maxn],kdtree[maxn];
double var[demension];
int split [maxn];
int cmpDem;
bool cmp(const Hotel &a,const Hotel &b){
	return a.pos[cmpDem]<b.pos[cmpDem];
}
void build (int l,int r){
	if (l>=r)return;
	int mid = l+r >>1;
	for (int i=0;i<demension;i++){
		double ave =0;
		for (int j=l;j<=r;j++){
			ave+=hotel[j].pos[i];
		}
		ave/=(r-l+1);
		var[i] =0;
		for (int j=l;j<=r;j++){
			var[i]+=pow(hotel[j].pos[i]-ave,2);
		}
		var[i]/=(r-l+1);
	}
	split[mid] =-1;
	double maxVar=-1;
	for (int i=0;i<demension;i++){
		if (var[i]>maxVar){
			maxVar = var[i];
			split[mid] =i;
		}
	}
	cmpDem = split[mid];
	nth_element(hotel+l,hotel+mid,hotel+r+1,cmp);
	build (l,mid-1);
	build (mid+1,r);
}
int ansIndex;
LL ansDis;
void query(int l,int r,const Hotel& x){
	if (l>r)return ;
	int mid = l+r >>1;
	LL dis =0;
	for (int i=0;i<demension;i++){
		dis +=1LL*(x.pos[i]-hotel[mid].pos[i])*(x.pos[i]-hotel[mid].pos[i]);
	}
	if (hotel[mid].c<=x.c){
		if (ansDis == dis && hotel[mid].id<hotel[ansIndex].id){
			ansIndex = mid;
		}else if (dis<ansDis){
			ansDis = dis;
			ansIndex = mid;
		} 
	}
	int d = split[mid];
	LL radius = 1LL*(x.pos[d]-hotel[mid].pos[d])*(x.pos[d]-hotel[mid].pos[d]);
	if (x.pos[d]<hotel[mid].pos[d]){
		query(l,mid-1,x);
		if (ansDis>radius){
			query(mid+1,r,x);
		}
	}else{
		query(mid+1,r,x);
		if (ansDis>radius){
			query(l,mid-1,x);
		}
	}
}
int T;
void input(){
	scanf("%d%d",&n,&m);
	for (int i=0;i<n;i++){
		scanf("%d%d%d",&hotel[i].pos[0],&hotel[i].pos[1],&hotel[i].c);
		hotel[i].id=i;
	}
	build (0,n-1);
}
void solve(){
	Hotel x;
	for (int i=1;i<=m;i++){
		scanf("%d%d%d",&x.pos[0],&x.pos[1],&x.c);
		ansDis = INF;
		ansIndex =n+1;
		query(0,n-1,x);
		printf("%d %d %d\n",hotel[ansIndex].pos[0],hotel[ansIndex].pos[1],hotel[ansIndex].c);
	}
}
int main(){
	scanf("%d",&T);
	while (T--){
		input();
		solve();
	}
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值