划分树是什么:
划分树是一种基于线段树的数据结构。主要用于快速求出(在
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,ql−1]内小于等于中位数的个数, 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,ql−1]中有 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+cntr−1], k k k保持不变; 2. 2. 2.如果我们进入右子树,新区间起始于 m i d + 1 mid+1 mid+1, [ l , q l − 1 ] [l,ql-1] [l,ql−1]中有 q l − l − c n t l ql-l-cntl ql−l−cntl个元素进入右子树, [ l , q r ] [l,qr] [l,qr]中有 q r − l − c n t r + 1 qr-l-cntr+1 qr−l−cntr+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+ql−l−cntl,mid+1+qr−l−cntr],且 k k k应该减去 c n t r − c n t l cntr-cntl cntr−cntl(即区间 [ 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;
};