划分树

划分树,类似线段树,主要用于求解某个区间的第k 大元素(时间复杂度log(n)),快排本也可以快速找出,但快排会改变原序列,所以每求一次都得恢复序列。

  下面就以 POJ 2104 进行解说:

  题目意思就是,给你n 个数的原序列,有m 次询问,每次询问给出l、r、k,求原序列l 到r 之间第k 大的数。n范围10万,m范围5千,这道题用快排也可以过,快排过的时间复杂度n*m,而划分树是m*logn(实际上应该是nlogn才对,因为建图时间是nlogn,n又比m大),分别AC后,时间相差很明显。

  划分树,顾名思义是将n 个数的序列不断划分,根结点就是原序列,左孩子保存父结点所有元素排序后的一半,右孩子也存一半,也就是说排名1 -> mid的存在左边,排名(mid+1) -> r 的存在右边,同一结点上每个元素保持原序列中相对的顺序。见下图:

   

  红点标记的就是进入左孩子的元素。

  当然,一般不会说每个结点开个数组存数,经观察,每一层都包含原本的n 个数,只是顺序不同而已,所以我们可以开val[20][N]来保存,也就是说共20层,每一层N个数。

  我们还需要一个辅助数组num,num[i]表示i 前面有多少数进入左孩子(i 和i 前面可以弄成本结点内也可以是所有,两种风格不同而已,下面采取的是本结点内),和val一样,num也开成num[20][N],来表示每一层,i 和i 前面(本结点)有多少进入左孩子。

  第一层:1 进入左孩子,num[1]=1,5 进入右孩子,num[2]=1,...,num[8]=4。

  第二层:5 进入左孩子,num[5]=1,6 进入右孩子,num[6]=1,...,num[8]=2。

  建图时就是维护每一层val[]和num[]的值就可以了。

 

查询:

 查询是在每一层的toleft的基础上进行区间的缩小,直到待查询区间缩小为1即为查询结果

总区间为[L,R],待查区间为[l,r];k是第K大值,toleft[r]-toleft[l-1]为区间[l,r]内被分配到左子数的个数.

toleft[r]-toleft[l-1]>=k

说明第k大值一定在左子树此时就可以更新区间,首先大区间二分为[L,L+R>>1],然后考虑小区间,可以确定的是,[l,r]分配在左子树的元素一定在区间[L,L+R>>1]内,所以,确定左边界sl=L+toleft[l-1]-toleft[L-1]:toleft[l-1]-toleft[L-1]为[L,l-1]内被分配到左子树的个数,他们不在查找之列,但相对位置不变,这些元素一定排在前面,以此来确定左边界,右边界就好确定了,因为toleft[r]-toleft[l-1]>=k,所以左边界加上toleft[r]-toleft[l-1]-1即可,即sr=sl+toleft[r]-toleft[l-1]-1,同时必有L<=sl,sr<=L+R>>1;

 toleft[r]-toleft[l-1]<k

此时,元素在右子树,二分大区间[L+R>>1|1,R],确定右边界,sr=r+toleft[R]-toleft[r],因为相对位置不变,toleft[R]-toleft[r]是分配在左子树的必定会往前移动,所以右边界往后移动,右边界确定后,确定左边界sl=sr-(l-r-toleft[l-1]-toleft[L-1]),减去分配到左子树的,剩下就在右子树。注意,此时要更新k k=k--toleft[l-1]-toleft[L-1],已经确定前面有toleft[l-1]-toleft[L-1]比其小,所以就是求右子树内区间[sl,sr]第k--toleft[l-1]-toleft[L-1]小。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值