2017.3.25 SJY摆石子 思考记录

        终于找到了一个kd树的题、、来看看


     k-d树原理还是不难的,就是代码技巧比较多可能可读性比较差

      它就是把一个平面分成:


找最接近的点原理是利用极值矩形:


这个题就是k-d树板子题了、

下面的注释很详细

码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 1000005
#define inf 1000000000 
using namespace std;
int xycmp,i,n,m,ans,rt,op;

struct dian
{                              //注意:下标范围为[2]的  0指的是x坐标  1指的是y坐标 
int xiao[2],da[2],xy[2];
}d[N];                   //对于d集的:只有xy有用,指每个点读入的坐标   

bool cmp(dian x,dian y) 
{   //cout<<xycmp<<" ";
	return (x.xy[xycmp]<y.xy[xycmp]);                            //比较的是这一次比较的坐标的大小 
}

struct kdtree
{
  	int xia[N][2];   //指向这个节点的两个儿子 
  	dian s[N],q;       //对于s集的:   xiao  指的是矩形左下角的坐标集    da指的是矩形右上角的点集      xy指的是这个点的坐标 
	void upd(int now)  //维护矩形 
	{
		int l=xia[now][0],r=xia[now][1]; 
		for(int i=0;i<2;i++)//依次循环 x、y坐标 
		{
		if(l){s[now].da[i]=max(s[l].da[i],s[now].da[i]);s[now].xiao[i]=min(s[now].xiao[i],s[l].xiao[i]);} //如果有左儿子,就取一遍左儿子x、y  矩形  的   坐标最值  再取最值 
		if(r){s[now].da[i]=max(s[r].da[i],s[now].da[i]);s[now].xiao[i]=min(s[now].xiao[i],s[r].xiao[i]);}//如果有右儿子,就取一遍右儿子x、y  矩形  的   坐标最值  再取最值 
    	}
	}


	void add(int now,dian t)//添加 
	{
		for(int i=0;i<2;i++)s[now].xiao[i]=s[now].da[i]=s[now].xy[i]=t.xy[i];	//初始化最大、最小、这个s集(kd树上的) 值都是 当前点的x、y坐标 
	}
	
		int dis(int now)
	{
		int lin=0;    
		for(int i=0;i<2;i++)lin+=max(s[now].xiao[i]-q.xy[i],0);
		for(int i=0;i<2;i++)lin+=max(q.xy[i]-s[now].da[i],0);
		return lin;
		 //这块比较难理解,如果第一个循环>0第二个循环就一定为0  反之亦然   所以它实际上算的是点q到 now点对应的矩形的 曼哈顿距离 
	}
	
	

	void build(int &k,int l,int r,int xydo)//建kd树   k值要返回,因为没有初值   xydo表示当前比较的什么坐标  0是x 1是y 
	{
		k=(l+r)>>1;   xycmp=xydo;   //用k作为编号方便(依次标也没人管你,这不是线段树,可以乱),   xycmp是比较的坐标 
		nth_element(d+l,d+k,d+r+1,cmp);  //这几乎是为kd树量身打造的函数,详情自行百度或见我 stl 算法总结 
		add(k,d[k]);   //加入这个中间点 
		if(l<k)build(xia[k][0],l,k-1,xydo^1); //建左子树 
		if(r>k)build(xia[k][1],k+1,r,xydo^1); //建右子数 
		upd(k);			//维护矩形 
	}   
	
	void ins(int k,int xydo)	  //插入点   k是当前已有点的编号     
	    {
	    	if(q.xy[xydo]<s[k].xy[xydo])   //因为小的在左子树  大的在右子树 
	    	{
	    		if(xia[k][0])ins(xia[k][0],xydo^1);
	    		else xia[k][0]=++n,add(n,q);//没有的话就找到了归宿   加一个 
	    		
			}else
			{
					if(xia[k][1])ins(xia[k][1],xydo^1);     //
	    		else xia[k][1]=++n,add(n,q);                // 同上 
				
			}
	       upd(k);	//不要忘了更新 
		}
		
	void zhao(int k)
	{
		ans=min(ans,(abs(q.xy[0]-s[k].xy[0])+abs(q.xy[1]-s[k].xy[1])));//答案取这个点和要找点的最小距离 
		int ld=(xia[k][0])?dis(xia[k][0]):inf;//如果在矩形内部就是0,在外部就是最小距离,没有这个子节点就是inf 
		int rd=(xia[k][1])?dis(xia[k][1]):inf;
		if(ld<rd)
		{
			if(ld<ans)zhao(xia[k][0]);if(rd<ans)zhao(xia[k][1]);//用访问顺序来优化(优先访问距离短的来更新答案,这样长的就有可能被卡掉) 
		}else
		{	if(rd<ans)zhao(xia[k][1]);if(ld<ans)zhao(xia[k][0]);
		}
	}
}kd;
int main()
{
	scanf("%d%d",&n,&m);
	
	for(i=1;i<=n;i++)
	{
		scanf("%d%d",&d[i].xy[0],&d[i].xy[1]);		
	}
	kd.build(rt,1,n,0);
	
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&op,&kd.q.xy[0],&kd.q.xy[1]);
		if(op==1)
		{
			kd.ins(rt,0);
			
		}else
		{
			ans=inf;kd.zhao(rt);printf("%d\n",ans);  
		}
	}
	return 0;
 } 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值