hdu4347 kd-tree询问k近点

70 篇文章 0 订阅
27 篇文章 0 订阅

亲测直接写暴力是过不了的,必须得加上神秘的剪枝技巧。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <set>
#include <vector>
#include <map>
#define ll long long

using namespace std;

const int N=50007;
const int K=6;

int n,m;

struct point{
    int a[K];
    int div;  // 按哪个维度划分
    bool lef;  // 是否是叶子节点
    ll dis;  // 离询问点的距离。注意这个在读入建树时不会用到,在进入队列时才用到
    void print(){
        printf("%d",a[0]);
        for (int i=1;i<m;i++) 
            printf(" %d",a[i]);
        puts("");
    }
    bool operator < (const point &t) const{
        return dis<t.dis;
    }
    point(){}
    point(point &t,ll d){
        for (int i=0;i<m;i++) a[i]=t.a[i];
        dis=d;
    }
}p[N],tar;

int cmp_NO;
inline bool cmp(point x,point y){
    return x.a[cmp_NO]<y.a[cmp_NO];
}

inline ll dis(point x,point y){
    ll ret=0;
    for (int i=0;i<m;i++) 
        ret+=(x.a[i]-y.a[i])*(x.a[i]-y.a[i]);
    return ret;
}

inline void bulid_kdt(int L,int R,int d){
    if (L>R) return;
    int mid=(L+R)>>1;
    cmp_NO=d;
    nth_element(p+L,p+mid,p+R+1,cmp);
    p[mid].div=d;
    if (L==R){
        p[L].lef=true;
        return;
    }
    bulid_kdt(L,mid-1,(d+1)%m);
    bulid_kdt(mid+1,R,(d+1)%m);
}

priority_queue<point> que;
int num,nownum;
ll ans;

inline void find_kd(int L,int R){
    if (L>R) return;
    
    int mid=(L+R)>>1;
    ll d=dis(p[mid],tar);
    if (p[mid].lef){
        if (nownum<num){
            nownum++;
            que.push(point(p[mid],d));
            ans=max(ans,d);
        }
        else if (ans>d){
            que.pop();
            que.push(point(p[mid],d));
            ans=que.top().dis;
        }
        return;
    }

    int t=tar.a[p[mid].div]-p[mid].a[p[mid].div];
    if (t>0){
        find_kd(mid+1,R);
        if (nownum<num){
            nownum++;
            que.push(point(p[mid],d));
            ans=max(ans,d);
            find_kd(L,mid-1);
        }
        else {
            if (ans>d){
                que.pop();
                que.push(point(p[mid],d));
                ans=que.top().dis;
            }
            if (ans>t*t) 
                find_kd(L,mid-1);
        }
    }
    else {
        find_kd(L,mid-1);
        if (nownum<num){
            nownum++;
            que.push(point(p[mid],d));
            ans=max(ans,d);
            find_kd(mid+1,R);
        }
        else{
            if (ans>d){
                que.pop();
                que.push(point(p[mid],d));
                ans=que.top().dis;
            }
            if (ans>t*t) 
                find_kd(mid+1,R);
        }
    }
}

inline void put(){
    if (que.empty()) return;
    point pp=que.top();
    que.pop();
    put();
    pp.print();
}

int main(){
    while (~scanf("%d%d",&n,&m)){
        for (int i=0;i<n;i++){
            for (int j=0;j<m;j++) 
                scanf("%d",&p[i].a[j]);
            p[i].lef=false;
        }

        bulid_kdt(0,n-1,0);  // 这一步相当于将原数组重新排了个序,先访问到的点放在中间

        int q;
        scanf("%d",&q);
        while (q--){
            for (int i=0;i<m;i++)
                scanf("%d",&tar.a[i]);
            while (!que.empty()) que.pop();
            scanf("%d",&num);
            nownum=0;
            ans=-1;
            find_kd(0,n-1);
            printf("the closest %d points are:\n",num);
            put();
        }
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值