ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

kd-tree

2020-02-26 21:56:21  阅读:214  来源: 互联网

标签:kd int tree mn tr mid ls mx


\(kd-tree\) 是一种比较毒瘤的数据结构,不是很常考,会的人也不是很多。但是NOI考过,省选也考过,有时候也可以干一点暴力的事。

一般带修,可插入,允许强制在线(只是麻烦一点),然后查询就稀奇古怪了,比如给个矩形,问矩形里有几个点(据说二维的可以树套树,可是我不会),或者给每个点一个权值,问矩形里点权和,再或者把矩形改成某种奇怪的解析式,比如圆、椭圆什么的。

关于 \(kd-tree\) ,我觉得它和线段树很像,都可以打标记,然后还可以扩展到k维,这就是为什么叫\(kd-tree\)

\(kd-tree\) 是采用循环分割的方法建树的。什么叫循环分割?每次按照某一维为关键字排序,取出中位数,然后以这个节点为根,把[l,mid-1],[mid+1,r]作为左右子树,进行分割。光说不太好理解,上代码。

int cmp_D;
bool cmp(const node &a,const node &b){return a.d[cmp_d]<b.d[cmp_d];}
int build(int l,int r,int D){
    int mid=l+r>>1;cmp_d=D;
    nth_element(t+l,t+mid,t+r+1,cmp);
    t[mid].mx[0]=t[mid].mn[0]=t[mid].d[0];
    t[mid].mx[1]=t[mid].mn[1]=t[mid].d[1];
    if(l!=mid)t[mid].ls=build(l,mid-1,D%k+1);
    if(r!=mid)t[mid].rs=build(mid+1,r,D%k+1);
    up(mid);return mid;
}

关于那个 \(nth\_lement\) 据说是均摊 \(O(n)\) 把中位数放到mid的位置,其余数据不保证有序(和sort很像)

然后就是 \(kd-tree\) 的优化了(它也是因为这个才那么快)包围盒

包围盒,就是这个节点底下的子树的最小、最大横坐标、纵坐标。可以在pushup的时候记录。还是看代码。

void pushup(int p) {
    tr[p].sz=1,tr[p].sum=tr[p].tp.val;
    tr[p].mn[0]=tr[p].mx[0]=tr[p].tp.d[0];
    tr[p].mn[1]=tr[p].mx[1]=tr[p].tp.d[1];
    if(tr[p].ls) {
        tr[p].mn[0]=min(tr[p].mn[0],tr[tr[p].ls].mn[0]);
        tr[p].mn[1]=min(tr[p].mn[1],tr[tr[p].ls].mn[1]);
        tr[p].mx[0]=max(tr[p].mx[0],tr[tr[p].ls].mx[0]);
        tr[p].mx[1]=max(tr[p].mx[1],tr[tr[p].ls].mx[1]);
        tr[p].sz+=tr[tr[p].ls].sz,tr[p].sum+=tr[tr[p].ls].sum;
    }
    if(tr[p].rs) {
        tr[p].mn[0]=min(tr[p].mn[0],tr[tr[p].rs].mn[0]);
        tr[p].mn[1]=min(tr[p].mn[1],tr[tr[p].rs].mn[1]);
        tr[p].mx[0]=max(tr[p].mx[0],tr[tr[p].rs].mx[0]);
        tr[p].mx[1]=max(tr[p].mx[1],tr[tr[p].rs].mx[1]);
        tr[p].sz+=tr[tr[p].rs].sz,tr[p].sum+=tr[tr[p].rs].sum;
    }
}

这种都可以灵活记录,主要看题目,但是mn,mx一般都会记下来。

然后说说为什么就快起来了。

有了包围盒,在查询的时候,比如上面要查询每个矩形内有几个点,就可以判断这个点的包围盒是否在矩形外,如果是,那么直接舍掉,因为不可能对答案产生贡献。否则,就像线段树一样进去分左右子树查询。

而且在数据随机的情况下,很可能一次少一颗子树,所以均摊 \(O(n\log n)\) ,而且求正交平面的时候严格 \(O(n\log n)\) (就是线段树)

例题1:

描述

有一列元素,每一个元素有三个属性:标号、标识符、数值。这些元素按照标号从1~n排列,标识符也是1~n的一个排列,初始时数值为0。当然我们可以把每个元素看成一个多维数字,那么这列元素就是一个数列。

现在请你维护这个数列,使其能支持以下两种操作:

1.将标号为l~r的所有元素的数值先乘上x,再加上y;

2.将标识符为l~r的所有元素的数值先乘上x,再加上y。

当然你还得回答某些询问:

1.标号为l~r的所有元素的数值的和;

2.标识符为l~r的所有元素的数值的和。

输入

第一行有两个正整数n、m,分别表示数列长度和操作与询问个数的总和。

第二行有n个正整数,表示每个元素的标识符,保证这n个数是1~n的一个排列。

接下来m行,每行的第一个数字为op。若op为0,则表示要进行第一个操作,接下去四个数字表示l,r,x,y;若op为1,则表示要进行第二个操作,接下去四个数字表示l,r,x,y;若op为2,则表示要回答第一个询问,接下去两个数字表示l,r;若op为3,则表示要回答第二个询问,接下去两个数字表示l,r。

输出

包含若干行,每行表示一个询问的答案。由于答案可能很大,只要请你输出答案对536870912取模后的值即可。

可以把 \((i,{p_i})\) 看作2维平面上的点,标识符是横着的矩形,区间是竖着的矩形,然后查询区间和,打个懒惰标记。cnt记录区间内有几个点,val是当前点的权值,sum是当前的区间和,这样懒标就可以下传了。其实这题和线段树很像

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=50005;
const int mod=536870912;
struct node{
    int d[2],mn[2],mx[2],ls,rs;
    int cnt,sum,val;
    int pl,ti;
}t[N];
int n,m,ans,rt,tpl,tti;
int op,l,r,x,y,dir,cmp_d;
void up(int u){
    t[u].cnt=1;
    if(t[u].ls){
        t[u].cnt+=t[t[u].ls].cnt;
        t[u].mx[0]=t[u].mx[0]>t[t[u].ls].mx[0]?t[u].mx[0]:t[t[u].ls].mx[0];
        t[u].mx[1]=t[u].mx[1]>t[t[u].ls].mx[1]?t[u].mx[1]:t[t[u].ls].mx[1];
        t[u].mn[0]=t[u].mn[0]<t[t[u].ls].mn[0]?t[u].mn[0]:t[t[u].ls].mn[0];
        t[u].mn[1]=t[u].mn[1]<t[t[u].ls].mn[1]?t[u].mn[1]:t[t[u].ls].mn[1];
    }
    if(t[u].rs){
        t[u].cnt+=t[t[u].rs].cnt;
        t[u].mx[0]=t[u].mx[0]>t[t[u].rs].mx[0]?t[u].mx[0]:t[t[u].rs].mx[0];
        t[u].mx[1]=t[u].mx[1]>t[t[u].rs].mx[1]?t[u].mx[1]:t[t[u].rs].mx[1];
        t[u].mn[0]=t[u].mn[0]<t[t[u].rs].mn[0]?t[u].mn[0]:t[t[u].rs].mn[0];
        t[u].mn[1]=t[u].mn[1]<t[t[u].rs].mn[1]?t[u].mn[1]:t[t[u].rs].mn[1];
    }
}
bool cmp(const node &a,const node &b){return a.d[cmp_d]<b.d[cmp_d];}
int build(int l,int r,int D){
    int mid=l+r>>1;cmp_d=D;
    nth_element(t+l,t+mid,t+r+1,cmp);
    t[mid].mx[0]=t[mid].mn[0]=t[mid].d[0];
    t[mid].mx[1]=t[mid].mn[1]=t[mid].d[1];
    if(l!=mid)t[mid].ls=build(l,mid-1,D^1);
    if(r!=mid)t[mid].rs=build(mid+1,r,D^1);
    up(mid);return mid;
}
void f(int x,int ti,int pl){
    t[x].val=(t[x].val*ti+pl)%mod;
    t[x].sum=(t[x].sum*ti+pl*t[x].cnt)%mod;
    t[x].ti=(t[x].ti*ti)%mod;
    t[x].pl=(t[x].pl*ti+pl)%mod;
}
void down(int u){
    if(t[u].pl==1&&t[u].ti==0)return;
    if(t[u].ls)f(t[u].ls,t[u].ti,t[u].pl);
    if(t[u].rs)f(t[u].rs,t[u].ti,t[u].pl);
    t[u].ti=1;t[u].pl=0;
}
void update(int u){
    if(t[u].mx[dir]<l||t[u].mn[dir]>r)return;
    if(l<=t[u].mn[dir]&&t[u].mx[dir]<=r){f(u,tti,tpl);return;}
    down(u);
    if(t[u].d[dir]>=l&&t[u].d[dir]<=r)t[u].val=(t[u].val*x+y)%mod;
    if(t[u].ls)update(t[u].ls);
    if(t[u].rs)update(t[u].rs);
    t[u].sum=(t[u].val+t[t[u].ls].sum+t[t[u].rs].sum)%mod;
}
void ask(int u)
{
    if(t[u].mx[dir]<l||t[u].mn[dir]>r)return;
    if(l<=t[u].mn[dir]&&t[u].mx[dir]<=r){ans=(ans+t[u].sum)%mod;return;}
    down(u);
    if(l<=t[u].d[dir]&&t[u].d[dir]<=r)ans=(ans+t[u].val)%mod;
    if(t[u].ls)ask(t[u].ls);
    if(t[u].rs)ask(t[u].rs);
 } 
signed main()
{
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;++i)scanf("%lld",&t[i].d[1]),t[i].d[0]=i;
    rt=build(1,n,0);
    while(m--){
        scanf("%lld",&op);
        if(op==0){
            scanf("%lld%lld%lld%lld",&l,&r,&x,&y);
            dir=0;tpl=y%mod;tti=x%mod;update(rt);
        }
        if(op==1){
            scanf("%lld%lld%lld%lld",&l,&r,&x,&y);
            dir=1;tpl=y%mod;tti=x%mod;update(rt);
        }
        if(op==2){
            scanf("%lld%lld",&l,&r);
            dir=0;ans=0;ask(rt);
            printf("%lld\n",ans);
        }
        if(op==3){
            scanf("%lld%lld",&l,&r);
            dir=1;ans=0;ask(rt);
            printf("%lld\n",ans);
        }
    } 
    return 0;
 } 

然后就是上面说到的强制在线的问题了。插入当然可以暴力把点塞进去,但是多了就会比较麻烦:因为数据可以不断地卡 \(kd-tree\) ,卡成一条链,时间复杂度就爆炸了。

对于那些不强制在线的题,可以直接把所有点先插进去,对于目前在树上和不在树上的点打标记,插入后更新标记就好了。

然而强制在线的话……

就要用到替罪羊树的思想拍平重构,所以会多一只 \(log\) 就是这么来的(据说可以treap重构,但是我不会qwq)

扔道重构树的例题:P4148 简单题

这题与上题相比不用记录那么多东西,就是强制在线比较恶心,要重构。

#include<bits/stdc++.h>
using namespace std;
const int N=200005;
int n,lastans,root;
struct point {
    int d[2],val;
} a[N];
struct node {
    int sum,ls,rs,mx[2],mn[2],sz;
    point tp;
} tr[N];
int tot,top,rub[N];
int min(const int &a,const int &b) {
    return a<b?a:b;
}
int max(const int &a,const int &b) {
    return a>b?a:b;
}
int newnode() {
    return top?rub[top--]:++tot;
}
void pushup(int p) {
    tr[p].sz=1,tr[p].sum=tr[p].tp.val;
    tr[p].mn[0]=tr[p].mx[0]=tr[p].tp.d[0];
    tr[p].mn[1]=tr[p].mx[1]=tr[p].tp.d[1];
    if(tr[p].ls) {
        tr[p].mn[0]=min(tr[p].mn[0],tr[tr[p].ls].mn[0]);
        tr[p].mn[1]=min(tr[p].mn[1],tr[tr[p].ls].mn[1]);
        tr[p].mx[0]=max(tr[p].mx[0],tr[tr[p].ls].mx[0]);
        tr[p].mx[1]=max(tr[p].mx[1],tr[tr[p].ls].mx[1]);
        tr[p].sz+=tr[tr[p].ls].sz,tr[p].sum+=tr[tr[p].ls].sum;
    }
    if(tr[p].rs) {
        tr[p].mn[0]=min(tr[p].mn[0],tr[tr[p].rs].mn[0]);
        tr[p].mn[1]=min(tr[p].mn[1],tr[tr[p].rs].mn[1]);
        tr[p].mx[0]=max(tr[p].mx[0],tr[tr[p].rs].mx[0]);
        tr[p].mx[1]=max(tr[p].mx[1],tr[tr[p].rs].mx[1]);
        tr[p].sz+=tr[tr[p].rs].sz,tr[p].sum+=tr[tr[p].rs].sum;
    }
}
int cmp_D;
bool cmp(const point &a,const point &b) {
    return a.d[cmp_D]<b.d[cmp_D];
}
int build(int l,int r,int D) {
    if(l>r)return 0;
    int mid=(l+r)>>1,p=newnode();
    cmp_D=D;
    nth_element(a+l,a+mid+1,a+r+1,cmp);
    tr[p].tp=a[mid];
    tr[p].ls=build(l,mid-1,D^1);
    tr[p].rs=build(mid+1,r,D^1);
    pushup(p);
    return p;
}
void beat(int p,int num) {//拍平
    if(tr[p].ls)beat(tr[p].ls,num);
    a[tr[tr[p].ls].sz+num+1]=tr[p].tp,rub[++top]=p;
    if(tr[p].rs)beat(tr[p].rs,tr[tr[p].ls].sz+num+1);
}
void check(int &p,int D) {//重构
    if(tr[p].sz*0.75<tr[tr[p].ls].sz||tr[p].sz*0.75<tr[tr[p].rs].sz)
        beat(p,0),p=build(1,tr[p].sz,D);
}
void ins(int &p,point o,int D) {
    if(!p) {
        p=newnode();
        tr[p].ls=tr[p].rs=0;
        tr[p].tp=o;
        pushup(p);
        return;
    }
    if(o.d[D]<=tr[p].tp.d[D])ins(tr[p].ls,o,D^1);
    else ins(tr[p].rs,o,D^1);
    pushup(p);
    check(p,D);
}
bool in(int x1,int y1,int x2,int y2,int X1,int Y1,int X2,int Y2) {
    return x1<=X1&&X2<=x2&&y1<=Y1&&Y2<=y2;
}
bool out(int x1,int y1,int x2,int y2,int X1,int Y1,int X2,int Y2) {
    return x1>X2||x2<X1||y1>Y2||y2<Y1;
}
int query(int p,int x1,int y1,int x2,int y2) {
    if(!p)return 0;
    int res=0;
    if(in(x1,y1,x2,y2,tr[p].mn[0],tr[p].mn[1],tr[p].mx[0],tr[p].mx[1]))return tr[p].sum;
    if(out(x1,y1,x2,y2,tr[p].mn[0],tr[p].mn[1],tr[p].mx[0],tr[p].mx[1]))return 0;
    if(in(x1,y1,x2,y2,tr[p].tp.d[0],tr[p].tp.d[1],tr[p].tp.d[0],tr[p].tp.d[1]))res+=tr[p].tp.val;
    res+=query(tr[p].ls,x1,y1,x2,y2)+query(tr[p].rs,x1,y1,x2,y2);
    return res;
}
int main() {
    scanf("%d",&n);
    int opt,X1,Y1,X2,Y2,A;
    while("fyy AK IOI") {//据说在这里字符串与"1"的效果是一样的。
        scanf("%d",&opt);
        if(opt==3)return 0;
        if(opt==1) {
            scanf("%d%d%d",&X1,&X2,&A);
            X1^=lastans,X2^=lastans,A^=lastans;
            ins(root,point {X1,X2,A},0);
        }
        if(opt==2) {
            scanf("%d%d%d%d",&X1,&Y1,&X2,&Y2);
            X1^=lastans,Y1^=lastans,X2^=lastans,Y2^=lastans;
            printf("%d\n",lastans=query(root,X1,Y1,X2,Y2));
        }
    }
}

此文将会持续更新,因为博主最近在练习 \(kd-tree\) ,更新完毕的时候将会发现这句话不见了qwq。

标签:kd,int,tree,mn,tr,mid,ls,mx
来源: https://www.cnblogs.com/zzctommy/p/12365164.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有