KDTree求平面最长最短点对

更新日志

前言

不会细致讲解KDT内容,如有需要,出门左转KDTree

这篇文章以最常用的二维点集为例(包括模板),其他维度同理。

思路(优化)

我们考虑2-D Tree,维护整个点集。

最朴素的做法是,每次都将当前节点与标准点更新答案,并进入其两个子树计算。不难发现,就是暴搜,没有意义。

引入一个重点:估价函数

这个函数用来估计一个区间与标准点可能的最大值。注意到,这个最大值不一定真实存在,所以称之为估价

更具体的,我们使用整个区间的最大最小坐标与标准点比较。

这个函数的具体作用在 query 部分,在进入一个子树前,先预估这个子树最理想的价值,如果最理想的情况都不比当前 res 更优,就无需进入这个区间了。

另一个优化是优先进入左右子树的顺序。我们看标准点在当前维度上位于中点的左侧或右侧,并以此优先进入可能更优的区间。

比如说获取最短点对,我们就优先进入同侧子树,反之同理。

模板

这个模板求曼哈顿距离下最短点对,其他距离同理。

这个模板内附了动态插入节点的功能(二进制分组)。

#define chmin(a,b) a=min(a,b)
#define chmax(a,b) a=max(a,b)
#define rep(i,x,y) for(int i=x;i<=y;i++)

const int inf=0x3f3f3f3f;
const int N=6e5+5,K=2,T=20;

int n,m;

struct node{
	int v[K];
	int mn[K],mx[K];
	int lson,rson;
}ns[N];
int ak;
bool cmp(int a,int b){return ns[a].v[ak]<ns[b].v[ak];}

inline int dis(int a,int b){
	return abs(ns[a].v[0]-ns[b].v[0])+abs(ns[a].v[1]-ns[b].v[1]);
}
inline int rec(int a,int b){
	int res=0;
	rep(k,0,K-1)res+=max(ns[b].v[k]-ns[a].mx[k],0)+max(ns[a].mn[k]-ns[b].v[k],0);
	return res;
}

struct kdtree{
	int acnt;
	int arr[N];
	int rt[T];
	void update(int x){
		rep(k,0,K-1){
			ns[x].mn[k]=ns[x].mx[k]=ns[x].v[k];
			if(ns[x].lson)chmin(ns[x].mn[k],ns[ns[x].lson].mn[k]),chmax(ns[x].mx[k],ns[ns[x].lson].mx[k]);
			if(ns[x].rson)chmin(ns[x].mn[k],ns[ns[x].rson].mn[k]),chmax(ns[x].mx[k],ns[ns[x].rson].mx[k]);
		}
	}
	int build(int l,int r,int k=0){
		int m=l+r>>1;
		ak=k;nth_element(arr+l,arr+m,arr+r+1,cmp);
		if(l<m)ns[arr[m]].lson=build(l,m-1,k^1);
		if(m<r)ns[arr[m]].rson=build(m+1,r,k^1);
		update(arr[m]);
		return arr[m];
	}
	void append(int &x){
		arr[++acnt]=x;
		if(ns[x].lson)append(ns[x].lson);
		if(ns[x].rson)append(ns[x].rson);
		x=0;
	}
	void insert(int x){
		acnt=0;
		arr[++acnt]=x;
		rep(t,0,T-1){
			if(rt[t])append(rt[t]);
			else{
				rt[t]=build(1,acnt);
				break;
			}
		}
	}
	void insert(int x,int y){
		n++;
		ns[n].v[0]=x;
		ns[n].v[1]=y;
		insert(n);
	}
	int query(int x,int st,int k){
		int res=inf;
		if(x!=st)chmin(res,dis(x,st));
		if(ns[st].v[k]<ns[x].v[k]){
			if(ns[x].lson&&rec(ns[x].lson,st)<res)chmin(res,query(ns[x].lson,st,k^1));
			if(ns[x].rson&&rec(ns[x].rson,st)<res)chmin(res,query(ns[x].rson,st,k^1));
		}else{
			if(ns[x].rson&&rec(ns[x].rson,st)<res)chmin(res,query(ns[x].rson,st,k^1));
			if(ns[x].lson&&rec(ns[x].lson,st)<res)chmin(res,query(ns[x].lson,st,k^1));
		}
		return res;
	}
	int query(int x,int y){
		ns[0].v[0]=x;ns[0].v[1]=y;
		int res=inf;
		rep(t,0,T-1)
			if(rt[t])res=min(res,query(rt[t],0,0));
		return res;
	}
}kdt;

附常用估价函数

统一规定 \(st\) 为标准点,\(now\) 为当前节点。

曼哈顿

最短

大致来说,在某一维度上,如果当前点在区间外,就取其到最近的边界的距离,否则就是 \(0\)

\[\max(now.x-st.x_{\max},0)+\max(st.x_{\min}-now.x,0)+\max(now.y-st.y_{\max},0)+\max(st.y_{\min}-now.y,0) \]

最长

容易理解,就是到边界的最大距离。

\[\max(|now.x-st.x_{\max}|,|now.x-st.x_{\min})|+\max(|now.y-st.y_{\max}|,|now.y-st.y_{\min}|) \]

欧几里得

前注:考虑精度问题,均不开方。

最短

同理,区间外则到边界,区间内则为 \(0\)

\[\max(now.x-st.x_{\max},st.x_{\min}-now.x,0)^2+\max(now.y-st.y_{\max},st.y_{\min}-now.y,0)^2 \]

最长

同理,到边界距离最大。

\[\max(now.x-st.x_{\max},now.x-st.x_{\min})^2+\max(now.y-st.y_{\max},now.y-st.y_{\min})^2 \]

例题

LG4169

代码

前注:非题解,不做详细讲解

#include<bits/stdc++.h>
using namespace std;

#define chmin(a,b) a=min(a,b)
#define chmax(a,b) a=max(a,b)
#define rep(i,x,y) for(int i=x;i<=y;i++)

const int inf=0x3f3f3f3f;
const int N=6e5+5,K=2,T=20;

int n,m;

struct node{
	int v[K];
	int mn[K],mx[K];
	int lson,rson;
}ns[N];
int ak;
bool cmp(int a,int b){return ns[a].v[ak]<ns[b].v[ak];}

inline int dis(int a,int b){
	return abs(ns[a].v[0]-ns[b].v[0])+abs(ns[a].v[1]-ns[b].v[1]);
}
inline int rec(int a,int b){
	int res=0;
	rep(k,0,K-1)res+=max(ns[b].v[k]-ns[a].mx[k],0)+max(ns[a].mn[k]-ns[b].v[k],0);
	return res;
}

struct kdtree{
	int acnt;
	int arr[N];
	int rt[T];
	void update(int x){
		rep(k,0,K-1){
			ns[x].mn[k]=ns[x].mx[k]=ns[x].v[k];
			if(ns[x].lson)chmin(ns[x].mn[k],ns[ns[x].lson].mn[k]),chmax(ns[x].mx[k],ns[ns[x].lson].mx[k]);
			if(ns[x].rson)chmin(ns[x].mn[k],ns[ns[x].rson].mn[k]),chmax(ns[x].mx[k],ns[ns[x].rson].mx[k]);
		}
	}
	int build(int l,int r,int k=0){
		int m=l+r>>1;
		ak=k;nth_element(arr+l,arr+m,arr+r+1,cmp);
		if(l<m)ns[arr[m]].lson=build(l,m-1,k^1);
		if(m<r)ns[arr[m]].rson=build(m+1,r,k^1);
		update(arr[m]);
		return arr[m];
	}
	void append(int &x){
		arr[++acnt]=x;
		if(ns[x].lson)append(ns[x].lson);
		if(ns[x].rson)append(ns[x].rson);
		x=0;
	}
	void insert(int x){
		acnt=0;
		arr[++acnt]=x;
		rep(t,0,T-1){
			if(rt[t])append(rt[t]);
			else{
				rt[t]=build(1,acnt);
				break;
			}
		}
	}
	void insert(int x,int y){
		n++;
		ns[n].v[0]=x;
		ns[n].v[1]=y;
		insert(n);
	}
	int query(int x,int st,int k){
		int res=inf;
		if(x!=st)chmin(res,dis(x,st));
		if(ns[st].v[k]<ns[x].v[k]){
			if(ns[x].lson&&rec(ns[x].lson,st)<res)chmin(res,query(ns[x].lson,st,k^1));
			if(ns[x].rson&&rec(ns[x].rson,st)<res)chmin(res,query(ns[x].rson,st,k^1));
		}else{
			if(ns[x].rson&&rec(ns[x].rson,st)<res)chmin(res,query(ns[x].rson,st,k^1));
			if(ns[x].lson&&rec(ns[x].lson,st)<res)chmin(res,query(ns[x].lson,st,k^1));
		}
		return res;
	}
	int query(int x,int y){
		ns[0].v[0]=x;ns[0].v[1]=y;
		int res=inf;
		rep(t,0,T-1)
			if(rt[t])res=min(res,query(rt[t],0,0));
		return res;
	}
}kdt;

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	rep(i,1,n){
		cin>>ns[i].v[0]>>ns[i].v[1];
		kdt.insert(i);
	}
	int t,x,y;
	while(m--){
		cin>>t>>x>>y;
		if(t==1)kdt.insert(x,y);
		if(t==2)cout<<kdt.query(x,y)<<"\n";
	}
	return 0;
}
posted @ 2024-11-21 22:37  HarlemBlog  阅读(2)  评论(0编辑  收藏  举报