盗了别人的资料。。。
其实看下面的一个图片就知道划分树的结构了,和归并排序极为相似:
注意:一定要注意其中中位数有多个相同的情况,需要统计。例如,上图第一次操作,只有一个3到了左区间。
下面是具体的讲解:
TonyShaw
如果对于一段区间,仅查找一次第k大元素的话好说,直接一个快排搞定。
如果有多次离线询问,然后就可以通过归并排序,建一棵归并树(nlogn)对于树的每一个节点,通过归并排序递归的建立一个序列,其中每个节点[l,r]表示原序列中,[l,r]这些数字排序以后的状态。如图,红色节点表示会被分到左子树。
就这样,在区间[l,r]查找第k大元素的时候,先二分枚举元素x,求出x为第几大元素,再在归并树里查找相应区间,对于每一个被包含的区间,二分查找有多少个比当前枚举的元素x小,有多少元素小于等于当前枚举元素x,如果k刚好在这两段区间里,x就是第k大数。程序巨猥琐于是我没写- -!总体复杂度n(logn)^3……强烈膜拜想出此算法的。
下面是本文重点:我不想写上面巨猥琐无比的算法,但是还要把题做出来,就从网上找资料,听说有个nlogn算法,叫做划分树。无奈资料巨少无比,只有某牛人的C++代码(一开始没看代码,光想下一位神牛的算法了,然后傻了……,代码写的很好,查找元素的函数对我帮助很大)和某“神牛”错误描述(此神牛浪费我2天时间,就是因为那个错误算法)。自己同样“yy3天后总算有点眉目,也不知道是不是传说中的划分树,不过复杂度nlogn应该是没错,“划分树和归并树差不多,不过节点定义不太一样。
划分树指的是,每一个节点保存区间[l,r]所有元素,元素排列顺序与原数组相同,但是两个子树的元素为该节点所有元素排序后前(r-l+1)/2个进入左子树,其余的到右子树,同时维护一个sum域,sum[i]表示l--i这些点中有多少个进入了左子树。
图片如下:
红色表示该元素要进入左子树。树的建立很简单,我的方法比较笨- -!先排序,然后递归建树l等于r时直接赋值为sorted[l],否则先处理其左右孩子,然后将其孩子安在原数组中的顺序在该节点排列,总而言之建树怎么都行,只要出来是上面这个图并且能完美的维护sum域就行。
查找其实是关键,因为再因查找[l,r]需要到某一点的左右孩子时需要把[l,r]更新。具体分如下几种情况讨论:
假设要在区间[l,r]中查找第k大元素,t为当前节点,lch,rch为左右孩子,left,mid为节点t左边界和中间点。
1、sum[r]-sum[l-1]>=k,查找lch[t],区间对应为[left+sum[l-1],left+sum[r]-1]。
2、sum[r]-sum[l-1]<k,查找rch[t],区间对应为[mid+1+l-left-sum[l-1],mid+r-left+1-sum[r]]。