KD-Tree一系列模板题

本文介绍了KD-Tree在解决曼哈顿距离问题中的应用,详细阐述了如何利用KD-Tree进行启发式搜索和剪枝。通过分析BZOJ上的几道题目,如BZOJ2716、BZOJ1941等,展示了KD-Tree在处理查询和插入操作中的策略,并讨论了在面对大量操作时的重构策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

刚学kd-tree,具体的操作网上都有,我就不赘述了。今天暂时只刷了一些曼哈顿距离的题目……都水水的,核心思想是记录出每个超平面(这个词好喜感=v=)的边缘坐标,利用估价函数判断询问点到两边超平面最近的距离是什么,然后先去那个超平面搜索,以达到启发式搜索和剪枝的目的。
这里写图片描述
具体来说,若要询问绿色的点,它到左边超平面最近的距离就是绿线的距离,我们就把这个距离作为估价函数就好了。(到右边的我没有画出来,但是估价函数会计算出0)

BZOJ2716 & 2648

双倍经验题+模板题,都是裸的询问离某个点曼哈顿距离最近的点是哪个点,直接用我上面说的算法就好了,询问的时候启发式搜索。

#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<algorithm>

#define maxn 500000+5
#define INF 0x3f3f3f3f

using namespace std;

int n,m,dim,ans,root,cnt;

struct Point{
    int x[2];
    Point(int a=0,int b=0){ x[0]=a; x[1]=b; }
    inline int& operator [](int i){ return x[i]; }
    inline bool operator <(const Point &T)const{ return x[dim]<T.x[dim]; }
}p[maxn];

struct KD_Tree{
    int l,r;
    int mx[2],mi[2]; //记录边界,使用估价函数剪枝,需要维护一个子树的平面空间范围
    Point t;
}tr[maxn<<1];

#define mid ((l+r)>>1)

inline int in(){
    int x=0,f=1; char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
    for(; isdigit(ch);ch=getchar()) x=x*10+ch-'0';
    return x*f;
}

inline void Update_mx(int &x,int y){ if(y>x) x=y; }

inline void Update_mi(int &x,int y){ if(y<x) x=y; }

inline int dis(Point a,Point b){
    return abs(a[0]-b[0])+abs(a[1]-b[1]);
}

int calc(int k,Point x){
    int res=0;
    for(int i=0;i<2;i++){
        res+=max(0,tr[k].mi[i]-x[i]);
        res+=max(0,x[i]-tr[k].mx[i]);
    }
    return res;
}

void Update(int k){
    for(int i=0;i<2;i++){
        if(tr[k].l) Update_mx(tr[k].mx[i],tr[tr[k].l].mx[i]),Update_mi(tr[k].mi[i],tr[tr[k].l].mi[i]);
        if(tr[k].r) Update_mx(tr[k].mx[i],tr[tr[k].r].mx[i]),Update_mi(tr[k].mi[i],tr[tr[k].r].mi[i]);
    }
}

void Build(int &k,int l,int r,int d){
    dim=d; k=++cnt;
    nth_element(p+l,p+mid,p+r+1);
    tr[k].t=p[mid];
    for(int i=0;i<2;i++)
        tr[k].mx[i]=tr[k].mi[i]=p[mid][i];
    if(l<mid) Build(tr[k].l,l,mid-1,d^1);
    if(mid<r) Build(tr[k].r,mid+1,r,d^1);
    Update(k);
}

void Insert(int &k,Point pt,int d){
    if(!k){
        k=++cnt;
        for(int i=0;i<2;i++) tr[k].mx[i]=tr[k].mi[i]=pt[i];
        tr[k].t=pt; return;
    }
    dim=d;
    if(pt<tr[k].t) Insert(tr[k].l,pt,d^1);
    else Insert(tr[k].r,pt,d^1);
    Update(k);
}

void Query(int k,Point pt,int d){
    if(!k) return;
    ans=min(ans,dis(pt,tr[k].t));
    int lv=INF,rv=INF;
    if(tr[k].l) lv=calc(tr[k].l,pt);
    if(tr[k].r) rv=calc(tr[k].r,pt);
    if(lv<rv){
        if(lv<ans) Query(tr[k].l,pt,d^1);
        if(rv<ans) Query(tr[k].r,pt,d^1);
    }
    else{
        if(rv<ans) Query(tr[k].r,pt,d^1);
        if(lv<ans) Query(tr[k].l,pt,d^1);       
    }
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("2648.in","r",stdin);
    freopen("2648.out","w",stdout);
#endif
    n=in(); m=in();
    for(int i=1;i<=n;i++){
        p[i][0]=in(); p[i][1]=in();    
    }
    Build(root,1,n,0);
    for(int opt,x,y,i=1;i<=m;i++){
        opt=in(); x=in(); y=in();
        if(opt==1) Insert(root,Point(x,y),0);       
        else{
            ans=INF;
            Query(root,Point(x,y),0);
            printf("%d\n",ans);
        }
    }
    return 0;
}

BZOJ1941

跟上面一样是模板题……只需要枚举每个点进行询问,更新最近最远点之间的差就好了。
然而这题要还询问最远点,怎么办呢?
仿照最小值的估价函数也造一个差不多的就好了呗……意义是离某个超平面可能达到的最远距离。
(我会告诉你我偷懒从上面复制代码改了改吗?)

#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<algorithm>

#define maxn 500000+5
#define INF 0x3f3f3f3f

using namespace std;

int n,m,dim,ans,ans1,ans2,root,cnt;

struct Point{
    int x[2];
    Point(int a=0,int b=0){ x[0]=a; x[1]=b; }
    inline int& operator [](int i){ return x[i]; }
    inline bool operator <(const Point &T)const{ return x[dim]<T.x[dim]; }
}p[maxn];

struct KD_Tree{
    int l,r;
    int mx[2],mi[2]; 
    Point t;
}tr[maxn<<1];

#define mid ((l+r)>>1)

inline int in(){
    int x=0,f=1; char ch=getchar();
    for(;!isdigit(ch
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值