划分树

划分树是什么:

划分树是一种基于线段树的数据结构。主要用于快速求出(在 l o g ( n ) log(n) log(n)的时间复杂度内)序列区间的第k小值。这不是主席树也能做吗,没错,但是划分树要比主席树快很多(至少快了 20 20 20%)。那么在某些题目中,用划分树+暴力过掉题目岂不是美滋滋

对划分树的理解:

先上一张图(源自这篇博客):
啊这
输入原序列是 [ 1 , 5 , 6 , 3 , 8 , 4 , 4 , 2 ] [1,5,6,3,8,4,4,2] [1,5,6,3,8,4,4,2],和树的第一(0)层相等,然后依据中位数,将数据分为两部分递归构建左右子树。现在来看一下具体是怎么划分的,不妨设我们当前位于第 i i i层,正在处理的区间是 [ l , r ] [l,r] [l,r],该区间的中位数为 x x x,且 m i d = ( l + r ) 2 mid={(l+r) \over 2} mid=2(l+r),我们把 [ l , r ] [l,r] [l,r] < = x <=x <=x的数依次放入左子树,不过此处有一个细节,不妨设从小到大排序后的序列中 [ l , m i d ] [l,mid] [l,mid]中等于 x x x的数有 y y y个,那么左子树中最多放入 y y y个等于 x x x的数,这样才能保证左子树的区间刚好是 [ l , m i d ] [l,mid] [l,mid]且右子树的区间刚好是 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]。按照这样划分出来的树就和上图保持一致了。

那么怎么查询某个区间的第 k k k大呢?我们不妨再令 n u m [ c ] [ i ] num[c][i] num[c][i]等于第 c c c层树 i i i所对应的 [ l , i ] [l,i] [l,i]内进入左子树的元素个数,这在构建树的过程中很容易统计出来。假设此时我们位于第 c c c层树,对应的区间为 [ l , r ] [l,r] [l,r],要查询 [ q l , q r ] [ql,qr] [ql,qr]内的第 k k k大,那么我们可以统计出来在 [ q l , q r ] [ql,qr] [ql,qr] < = <= <=区间 [ l , r ] [l,r] [l,r]中位数的元素个数,若它 < = k <=k <=k,显然我们应该递归查询左子树,否则应该递归查询右子树。此时对应的查询区间也应该修改,这里需要细心的推导。不妨设 c n t l cntl cntl等于 [ l , q l − 1 ] [l,ql-1] [l,ql1]内小于等于中位数的个数, c n t r cntr cntr等于 [ l , q r ] [l,qr] [l,qr]内小于等于中位数的个数,下面开始分类讨论: 1. 1. 1.如果我们进入左子树,新区间起始于 l l l [ l , q l − 1 ] [l,ql-1] [l,ql1]中有 c n t l cntl cntl个元素进入左子树, [ l , q r ] [l,qr] [l,qr]中有 c n t r cntr cntr个元素进入左子树,那么新区间就等于 [ l + c n t l , l + c n t r − 1 ] [l+cntl,l+cntr-1] [l+cntl,l+cntr1] k k k保持不变; 2. 2. 2.如果我们进入右子树,新区间起始于 m i d + 1 mid+1 mid+1 [ l , q l − 1 ] [l,ql-1] [l,ql1]中有 q l − l − c n t l ql-l-cntl qllcntl个元素进入右子树, [ l , q r ] [l,qr] [l,qr]中有 q r − l − c n t r + 1 qr-l-cntr+1 qrlcntr+1个元素进入右子树,那么新区间就等于 [ m i d + 1 + q l − l − c n t l , m i d + 1 + q r − l − c n t r ] [mid+1+ql-l-cntl,mid+1+qr-l-cntr] [mid+1+qllcntl,mid+1+qrlcntr],且 k k k应该减去 c n t r − c n t l cntr-cntl cntrcntl(即区间 [ q l , q r ] [ql,qr] [ql,qr]内进入左子树的元素个数)。

至此划分树就结束了,从查询的过程我们不难发现,构建树时的依次放入是非常重要的,它保证了元素的相对顺序没有变换,是我们在查询时转换查询区间的关键依据。

c o d e : code: code

class DivideTree
{
public:
    DivideTree(vector<int>& nums):vec(nums)
    {
        tree.emplace_back(nums);
        sort(vec.begin(),vec.end());
        build(0,0,vec.size()-1);
    }

    void build(int depth,int l,int r)
    {
        if(l==r)
            return ;
        if(tree.size()<=depth+1)
            tree.emplace_back(vector<int>(vec.size()));
        if(num.size()<=depth)
            num.emplace_back(vector<int>(vec.size()));
        int mid=(l+r)>>1,mid_value=vec[mid];
        int lidx=l,ridx=mid+1,lnum=0;
        for(int i=l;i<=mid;i++)
            if(vec[i]==mid_value)
                ++lnum;
        for(int i=l;i<=r;i++)
        {
            if(i==l)
                num[depth][i]=0;
            else
                num[depth][i]=num[depth][i-1];
            if(tree[depth][i]==mid_value)
            {
                if(lnum)
                {
                    --lnum;
                    tree[depth+1][lidx++]=mid_value;
                    ++num[depth][i];
                }
                else
                    tree[depth+1][ridx++]=mid_value;
            }
            else if(tree[depth][i]<mid_value)
            {
                tree[depth+1][lidx++]=tree[depth][i];
                ++num[depth][i];
            }
            else
                tree[depth+1][ridx++]=tree[depth][i];
        }
        build(depth+1,l,mid);
        build(depth+1,mid+1,r);
    }

    int query(int l,int r,int k)
    {
        return _query(0,0,vec.size()-1,l,r,k);
    }

    int _query(int depth,int l,int r,int ql,int qr,int k)
    {
        if(l==r)
            return tree[depth][l];
        int cntl,cntr,mid=(l+r)>>1;
        if(l==ql)
            cntl=0;
        else
            cntl=num[depth][ql-1];
        cntr=num[depth][qr];
        if(k<=cntr-cntl)
            return _query(depth+1,l,mid,l+cntl,l+cntr-1,k);
        else
            return _query(depth+1,mid+1,r,mid+1+ql-l-cntl,mid+1+qr-l-cntr,k-cntr+cntl);
    }
private:
    vector<vector<int>> tree;
    vector<vector<int>> num;
    vector<int> vec;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值