【BZOJ 4520】【CQOI 2016】K远对

kd树很早就像练一下了但是一直拖到现在。网上找了很多玄学的资料都看不懂,但是直接做了题后马上就懂了。
首先考虑k=1、也就是最远对时的kd树做法。
kd树可以看做每次对k维空间二分。一维就是二分,二维就是想切正方形一样先切成两半,然后再切一刀变成四个小正方形。每次取出一个点,假设目前找到的最大距离为d,就要找有没有和当前点距离大于d的点。如果暴力搜索复杂度是平方,但是如果用kd树,预先存下“当前区间内可能的x/y坐标最大/小数”就可以得出理论上的最远距离,比较一下,如果这个最远距离都小于d那就直接退出。
放到k远对也是一样,只不过之前比较的是最大值,现在比较第k大值,用一个优先队列维护即可。
有一个要注意的地方就是,因为一条线段会被两个点各算一次,如果只维护k个值就会可能只有k/2条线段,解决的方法就是维护2k个值,输出第2k个值即可。
PS kd树的巧妙就在于calc_dis这个函数算的只是理论最大值,因为横坐标差最大值和纵坐标差最大值不一定同时取在一个点上,但这一定是一个上限值,应该算是一个必要但是不充分的条件,但我还是不知道kd树的复杂度怎么证明。。。
PS的PS 最远点对可以用旋转卡壳的方法做,听说这道题也能,就是不断从k-1远对、k-2远对。。向后递推。看起来很神奇的样子。

#include<cmath>
#include<cstdio>
#include<vector>
#include <queue>
#include<cstring>
#include<iomanip>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#define ll long long
#define inf 1000000000
#define mod 1000000007
#define N 200000
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
priority_queue<ll,vector<ll>,greater<ll> > q;
struct kd_tree{int d[2],Mx[2],Mn[2],ls,rs;} tree[N];
int n,k,i,root,Mrk;
int nw[2];
void update(int rt)
{
    int l = tree[rt].ls; int r = tree[rt].rs; int i;
    fo(i,0,1)
        {
            if (l > 0) tree[rt].Mx[i] = max(tree[rt].Mx[i],tree[l].Mx[i]),tree[rt].Mn[i] = min(tree[rt].Mn[i],tree[l].Mn[i]);
            if (r > 0) tree[rt].Mx[i] = max(tree[rt].Mx[i],tree[r].Mx[i]),tree[rt].Mn[i] = min(tree[rt].Mn[i],tree[r].Mn[i]);
        }
}
bool cmp(const kd_tree &x,const kd_tree &y) {return x.d[Mrk]<y.d[Mrk];}
void build(int &rt,int l,int r,int mrk)
{
    int mid = (l + r) >> 1; Mrk = mrk; rt = mid;
    nth_element(tree+l,tree+mid,tree+r+1,cmp);
    tree[rt].Mx[0] = tree[rt].Mn[0] = tree[rt].d[0];
    tree[rt].Mx[1] = tree[rt].Mn[1] = tree[rt].d[1];
    if (l < mid) build(tree[rt].ls,l,mid-1,mrk^1);
    if (r > mid) build(tree[rt].rs,mid+1,r,mrk^1);
    update(rt);
}
ll sqr(const int &x) {return (ll)x*x;}
ll calc_dis(int k)
{
    if (k == 0) return 0;
    return  max(sqr(tree[k].Mx[0]-nw[0]),sqr(tree[k].Mn[0]-nw[0]))+
            max(sqr(tree[k].Mx[1]-nw[1]),sqr(tree[k].Mn[1]-nw[1]));
}
void query(int k)
{
    ll dis = sqr(tree[k].d[0] - nw[0]) + sqr(tree[k].d[1] - nw[1]);
    if (dis > q.top()) {q.pop(); q.push(dis);}
    ll dis_l = calc_dis(tree[k].ls); ll dis_r = calc_dis(tree[k].rs);
    if (dis_l > dis_r)
        {
            if (dis_l > q.top()) query(tree[k].ls);
            if (dis_r > q.top()) query(tree[k].rs);
        }   else
        {
            if (dis_r > q.top()) query(tree[k].rs);
            if (dis_l > q.top()) query(tree[k].ls);
        }
}


int main()
{
    scanf("%d%d",&n,&k);
    fo(i,1,n) scanf("%d%d",&tree[i].d[0],&tree[i].d[1]);
    build(root,1,n,0);
    fo(i,1,k*2) q.push(0);
    fo(i,1,n)
        {
            nw[0] = tree[i].d[0]; nw[1] = tree[i].d[1];
            query(root);
        }
    printf("%lld\n",q.top());
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值