洛谷 P4169 [Violet]天使玩偶/SJY摆棋子(模板 带插入K-D tree)

题目链接
说起来k-d tree这玩意也很暴力啊
建树的话每次取剩下数列下一维中位数的点做儿子(其实随机哪维也行,就跟点分找重心一样,这只是一个复杂度的问题
查询就是一个暴力剪枝
估价为这个子树代表的包含所有点的矩形离查询点的最近距离(具体方法一言难尽,直接看估价函数就好懂了
插入就直接二分插入,但是这样显然是会被卡成链之类的东西的,显然像替罪羊树一样定期重构就可以了

#include<bits/stdc++.h>
#define alpha 0.8
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;

inline long long read() { long long f=1,x=0;char ch; do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9'); do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9'); return f*x; }

struct dd
{
	long long d[2];
}tmp[500010];

struct kdtree
{
	int son[2],mi[2],mx[2],sz;
	dd p;
}t[2000010];

int cnt,D,sta[2000030],top,tot;
int n,m,rt;

int cmp(dd a,dd b)
{
	return a.d[D]<b.d[D];
}

inline int bad(int now)
{
	return alpha*t[now].sz<max(t[t[now].son[0]].sz,t[t[now].son[1]].sz);
}

inline int newnode()
{
	if(top) return sta[top--];
	else return ++tot;
}

void push_up(int now)
{
	int l=t[now].son[0];
	int r=t[now].son[1];
	for(int i=0;i<=1;i++)
	{
		t[now].mi[i]=t[now].mx[i]=t[now].p.d[i];
		if(l) t[now].mi[i]=min(t[now].mi[i],t[l].mi[i]),t[now].mx[i]=max(t[now].mx[i],t[l].mx[i]);
		if(r) t[now].mi[i]=min(t[now].mi[i],t[r].mi[i]),t[now].mx[i]=max(t[now].mx[i],t[r].mx[i]);
	}
	t[now].sz=t[l].sz+t[r].sz+1;
}

void pia(int now)
{
	if(!now) return ;
	pia(t[now].son[0]);
	tmp[++cnt]=t[now].p;
	sta[++top]=now;	
	pia(t[now].son[1]);
}

void la(int l,int r,int &now,int d)
{
	if(l>r) {now=0;return ;}
	int mid=(l+r)>>1;
	D=d,nth_element(tmp+l,tmp+mid,tmp+r+1,cmp);
	now=newnode();
	t[now].p=tmp[mid];
	la(l,mid-1,t[now].son[0],d^1);
	la(mid+1,r,t[now].son[1],d^1);
	push_up(now);
}

void rebuild(int &now)
{
	cnt=0;
	pia(now);
	if(cnt>0) la(1,cnt,now,0);
}

void insert(int &now,int d,dd pp)
{
	if(!now)
	{
		now=newnode();
		t[now].p=pp;
		t[now].son[0]=t[now].son[1]=0;
		push_up(now);
		return ;
	}
	if(t[now].p.d[d]>=pp.d[d]) insert(t[now].son[0],d^1,pp);
	else insert(t[now].son[1],d^1,pp);
	push_up(now);
	if(bad(now)) rebuild(now); 
}

inline int get_dis(int now,dd pp)
{
	int dis=0;
	for(int i=0;i<=1;i++)
	{
		dis+=max(t[now].mi[i]-pp.d[i],0ll)+max(pp.d[i]-t[now].mx[i],0ll);
	}
	return dis;
}

long long dist(dd a,dd b)
{
	return abs(a.d[0]-b.d[0])+abs(a.d[1]-b.d[1]);
}

long long ans=inf;

void query(int now,dd pp)
{
	ans=min(ans,dist(t[now].p,pp));
	long long d1=inf,d2=inf;
	if(t[now].son[0]) d1=get_dis(t[now].son[0],pp);
	if(t[now].son[1]) d2=get_dis(t[now].son[1],pp);
	if(d1<=d2)
	{
		if(d1<ans) query(t[now].son[0],pp);
		if(d2<ans) query(t[now].son[1],pp);
	}
	else
	{
		if(d2<ans) query(t[now].son[1],pp);
		if(d1<ans) query(t[now].son[0],pp);
	}
}

int kd,l,r;

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		tmp[i].d[0]=read();
		tmp[i].d[1]=read();
	}
	la(1,n,rt,0);
	dd tmp1;
	for(int i=1;i<=m;i++)
	{
		kd=read();
		tmp1.d[0]=read();
		tmp1.d[1]=read();
		if(kd==1) insert(rt,0,tmp1);
		else ans=inf,query(rt,tmp1),printf("%lld\n",ans);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值