SDOI2010 捉迷藏 —— KD-Tree

Posted ylsoi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SDOI2010 捉迷藏 —— KD-Tree相关的知识,希望对你有一定的参考价值。

SDOI2010 捉迷藏

对于i=1...n,求曼哈顿距离距离i最近和最远的点的距离分别是多少。

思路

KD-Tree 的模板题目。

KD-Tree,实际上就是对一个多维空间进行不断的划分,在一维上类似于二叉搜索树。

如果是多维的,我们可以每一次只划分一维,然后这样不断轮流划分不同的维度。

具体的,对于当前维度的划分,我们最优秀的策略就是取所有点按照当前维度排序之后从中点划分开来,这样可以保证整颗树尽量的平衡。

对于每一次划分应该选择哪个维度,我们其实可以选择维度方差最大的那个维度进行划分,这样可以保证复杂度尽量地优秀。

这个题目需要寻找平面内最近/最远点,考虑如何实现实现上述查询,我们可以计算出当前的点距离当前矩形的左右儿子的最小/最大距离,优先递归答案可能会更加优秀的那个点,如果目前的区间的最优的答案都比目前得到的最优答案要劣,那么我们就可以停止递归了。

不难发现上面的查询操作本质上是一个搜索+剪枝,KD-Tree独特的构造方法使得一般情况下复杂度为\(\log n\),最坏复杂度为\(\sqrt n\)

代码参考了黄学长的方法。

/*=======================================
 * Author : ylsoi
 * Time : 2019.3.25
 * Problem : bzoj1941
 * E-mail : [email protected]
 * ====================================*/
#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
#define debug(x) cout<<#x<<"="<<x<<" "
#define fi first
#define se second
#define mk make_pair
#define pb push_back
#define pii pair<int,int>
typedef long long ll;

using namespace std;

void File(){
    freopen("bzoj1941.in","r",stdin);
    freopen("bzoj1941.out","w",stdout);
}

template<typename T>void read(T &_){
    _=0; T f=1; char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
    for(;isdigit(c);c=getchar())_=(_<<1)+(_<<3)+(c^'0');
    _*=f;
}

string proc(){
    ifstream f("/proc/self/status");
    return string(istreambuf_iterator<char>(f),istreambuf_iterator<char>());
}

const int maxn=5e5+10;
const int inf=0x3f3f3f3f;
int n,rt,ans=inf;

struct node{
    int lc,rc,mn[2],mx[2],d[2];
    int dis(const node & b) const {
        return abs(d[0]-b.d[0])+abs(d[1]-b.d[1]);
    }
}t[maxn];
bool cmp0(node a,node b){return a.d[0]<b.d[0];}
bool cmp1(node a,node b){return a.d[1]<b.d[1];}

bool chkmax(int &x,int y){return x<y ? x=y,1 : 0;}
bool chkmin(int &x,int y){return x>y ? x=y,1 : 0;}

void pushup(int o){
    int lc=t[o].lc,rc=t[o].rc;
    t[o].mn[0]=t[o].mx[0]=t[o].d[0];
    t[o].mn[1]=t[o].mx[1]=t[o].d[1];
    REP(i,0,1){
        if(lc)chkmin(t[o].mn[i],t[lc].mn[i]);
        if(rc)chkmin(t[o].mn[i],t[rc].mn[i]);
        if(lc)chkmax(t[o].mx[i],t[lc].mx[i]);
        if(rc)chkmax(t[o].mx[i],t[rc].mx[i]);
    }
}

void build(int &o,int l,int r,int now){
    if(l>r)return;
    o=(l+r)>>1;
    nth_element(t+l,t+o,t+r+1,!now ? cmp0 : cmp1);
    build(t[o].lc,l,o-1,now^1);
    build(t[o].rc,o+1,r,now^1);
    //debug(o)<<endl;
    //debug(t[o].d[0]),debug(t[o].d[1])<<endl;
    pushup(o);
}

int f_mn(int d[],int u){
    int ret=0;
    REP(i,0,1){
        ret+=max(t[u].mn[i]-d[i],0);
        ret+=max(d[i]-t[u].mx[i],0);
    }
    return ret;
}

int f_mx(int d[],int u){
    int ret=0;
    REP(i,0,1)ret+=max(abs(d[i]-t[u].mn[i]),abs(d[i]-t[u].mx[i]));
    return ret;
}

void query_mn(int id,int d[],int o,int &now){
    if(!o || f_mn(d,o)>=now)return;
    if(id!=o)now=min(now,t[id].dis(t[o]));
    if(f_mn(d,t[o].lc)<f_mn(d,t[o].rc)){
        query_mn(id,d,t[o].lc,now);
        query_mn(id,d,t[o].rc,now);
    }
    else{
        query_mn(id,d,t[o].rc,now);
        query_mn(id,d,t[o].lc,now);
    }
}

void query_mx(int id,int d[],int o,int &now){
    if(!o || f_mx(d,o)<=now)return;
    if(id!=o)now=max(now,t[id].dis(t[o]));
    if(f_mx(d,t[o].lc)>f_mx(d,t[o].rc)){
        query_mx(id,d,t[o].lc,now);
        query_mx(id,d,t[o].rc,now);
    }
    else{
        query_mx(id,d,t[o].rc,now);
        query_mx(id,d,t[o].lc,now);
    }
}

int main(){
    File();

    read(n);
    REP(i,1,n)read(t[i].d[0]),read(t[i].d[1]);

    build(rt,1,n,0);

    REP(i,1,n){
        int d[2]={t[i].d[0],t[i].d[1]};
        int mx_dis=-inf,mn_dis=inf;
        query_mx(i,d,rt,mx_dis);
        query_mn(i,d,rt,mn_dis);
        ans=min(ans,mx_dis-mn_dis);
    }

    printf("%d\n",ans);

    return 0;
}

以上是关于SDOI2010 捉迷藏 —— KD-Tree的主要内容,如果未能解决你的问题,请参考以下文章

luoguP2479 [SDOI2010]捉迷藏

[SDOI2010]捉迷藏 K-Dtree

[SDOI2010]捉迷藏

题解[SDOI2010]捉迷藏

[BZOJ1941][Sdoi2010]Hide and Seek

bzoj1941 [Sdoi2010]Hide and Seek