zoukankan      html  css  js  c++  java
  • 划分树

    定义

    划分树是一种基于线段树的数据结构。主要用于快速求出序列区间的第k大值。

    划分树的根节点存储原数列
    每个节点的左子节点存储它元素中较小的一半,右子节点存储它元素中较大的一半
    并且同一个节点中元素的排列和原数列一样

    例如1,5,3,4,2构成的划分树如下图所示

    具体操作

      • 建树

        我们发现每层所有节点的元素个数和都是原数列长度
        所以我们可以用数组按层存储

        我们用partition_tree[i][j]存储第i层的第j个元素,用num[i][j]表示在第i层的第j个元素之前有多少节点进入了它所在节点的左子树(包括它自己)
        由于每个子节点建立要用到父节点的元素,所以我们可以递归按中序建树

        void build(int deep,int l,int r){//构造第deep层,区间为[l,r]的子节点 
            if(l==r)return;
            int mid=(l+r)>>1,x=1;//x记录有多少和sorted[mid](当前节点元素的中位数)一样大的数进入左子节点 
            for(int i=mid-1;i>=l;i--,x++){
                if(sorted[i]<sorted[mid])break;//sorted是排好序的数组 
            }
            for(int i=l,j=l,k=mid+1;i<=r;i++){
                num[deep][i]=(i==l)?0:num[deep][i-1];
                if(partition_tree[deep][i]<sorted[mid]||(partition_tree[deep][i]==sorted[mid]&&(x--)>0)){//进入左子节点 
                    partition_tree[deep+1][j++]=partition_tree[deep][i],num[deep][i]++;
                }
                else partition_tree[deep+1][k++]=partition_tree[deep][i];//进入右子节点 
            } 
            build(deep+1,l,mid);//构建子节点 
            build(deep+1,mid+1,r);
        }
      • 查询(区间第k大)

        我们如果要查找某一层[l,r]的第k大
        首先我们可以求出l之前有多少个数进入了左子节点x1=num[deep][l-1]
        和[l,r]中有多少个数进入了左子节点x2=(num[deep][r]-num[deep][l-1])

        如果x2$ge$k,我们去它的左子节点找[l,r]在左子节点中所在区间的第k大即可
        因为l之前有x1个数进入左子节点,[l,r]有x2个数进入左子节点,并且左子节点区间为[L,mid]
        所以[l,r]在左子节点中所在区间为[L+x1,L+x1+x2-1](x1+x2=num[deep][r])

        如果x2<k,我们去它的右子节点找[l,r]在右子节点中所在区间的第k-x2大即可
        因为l之前有l-L-x1个数进入右子节点,[l,r]有r-l+1-x2个数进入右子节点,并且右子节点的区间为[mid+1,R]
        所以[l,r]在右子节点中所在区间为[l-L-x1+mid+1,l-L-x1+mid+1+(r-l+1-x2)-1]=[l-L-x1+mid+1,r-L-x1+mid+1-x2](-x1-x2=-num[deep][r])

        如果是叶子节点直接返回即可

        int query(int deep,int L,int R,int l,int r,int k){//询问[l,r]中第k大的数,L,R为当前节点的区间范围 
            if(L==R)return partition_tree[deep][L];//叶子节点就直接返回 
            int mid=(L+R)>>1,x1=(L==l)?0:num[deep][l-1],x2=num[deep][r]-x1;//x1表示当前节点中在l之前有多少点进入了左子树,x2表示当前节点在[l,r]中有多少点进入左子树 
            if(x2>=k)return query(deep+1,L,mid,x1+L,L+num[deep][r]-1,k);//在左子树中
            else return query(deep+1,mid+1,R,l-L-x1+mid+1,mid+r-L-num[deep][r]+1,k-x2);//在右子树中 
        }

    例题hdu2665 Kth number

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    #define maxn 100005
    int a[maxn],sorted[maxn],partition_tree[20][maxn],num[20][maxn],n,m;
    void build(int deep,int l,int r){//构造第deep层,区间为[l,r]的子节点 
        if(l==r)return;
        int mid=(l+r)>>1,x=1;//x记录有多少和sorted[mid](当前节点元素的中位数)一样大的数进入左子节点 
        for(int i=mid-1;i>=l;i--,x++){
            if(sorted[i]<sorted[mid])break;//sorted是排好序的数组 
        }
        for(int i=l,j=l,k=mid+1;i<=r;i++){
            num[deep][i]=(i==l)?0:num[deep][i-1];
            if(partition_tree[deep][i]<sorted[mid]||(partition_tree[deep][i]==sorted[mid]&&(x--)>0)){//进入左子节点 
                partition_tree[deep+1][j++]=partition_tree[deep][i],num[deep][i]++;
            }
            else partition_tree[deep+1][k++]=partition_tree[deep][i];//进入右子节点 
        } 
        build(deep+1,l,mid);//构建子节点 
        build(deep+1,mid+1,r);
    }
    int query(int deep,int L,int R,int l,int r,int k){//询问[l,r]中第k大的数,L,R为当前节点的区间范围 
        if(L==R)return partition_tree[deep][L];//叶子节点就直接返回 
        int mid=(L+R)>>1,x1=(L==l)?0:num[deep][l-1],x2=num[deep][r]-x1;//x1表示当前节点中在l之前有多少点进入了左子树,x2表示当前节点在[l,r]中有多少点进入左子树 
        if(x2>=k)return query(deep+1,L,mid,x1+L,L+num[deep][r]-1,k);//在左子树中
        else return query(deep+1,mid+1,R,l-L-x1+mid+1,mid+r-L-num[deep][r]+1,k-x2);//在右子树中 
    }
    void work(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)scanf("%d",a+i),partition_tree[0][i]=sorted[i]=a[i];
        sort(sorted+1,sorted+1+n);
        build(0,1,n);
        int l,r,k;
        for(int i=0;i<m;i++){
            scanf("%d%d%d",&l,&r,&k);
            printf("%d
    ",query(0,1,n,l,r,k));
        }
    }
    int main(){
    //    freopen("input.txt","r",stdin);
        int t;scanf("%d",&t);
        while(t--)work();
        return 0;
    } 
  • 相关阅读:
    vc枚举本机端口信息API
    xmpp 与服务器连接并身份验证成功
    xmpp 常见错误 一
    xmpp 环境配置
    使用宏定义创建单例
    iOS知识列表
    ios sqlite 简单使用
    关于游戏
    开通博客园博客的第一天
    如何让移动设备的宽与浏览器视图的宽保持一致
  • 原文地址:https://www.cnblogs.com/bennettz/p/8343806.html
Copyright © 2011-2022 走看看