定义
归并树是线段树和归并排序的合成,它利用线段树将归并排序的每一步都记录下来
例如我们对1,5,3,4,2进行归并排序,就可以生成下面的归并树

归并树的每个父节点就是两个子节点归并排序后的结果
并且归并树的叶子节点的顺序是初始序列的顺序
用处
可以快速求出在原序列的一个区间中比某个数小(大)的有多少个数
于是就可以求区间第k大问题
存储方法
我们发现归并树的每一层数字个数不会超过原数列,所以我们用一个深度*原数列长度的二维数组就可以记录下来
具体操作
-
建树
由于每个节点是由它的两个子节点归并后构造出来的,所以我们可以递归构造子节点,回溯时构造父节点
void build(int deep,int l,int r){//建树 if(l==r){Merge[deep][l]=a[l];return;}//叶子节点 int mid=(l+r)>>1; build(deep+1,l,mid),build(deep+1,mid+1,r);//先构造子节点 for(int i=l,j=mid+1,k=l;i<=mid||j<=r;){//归并排序构造当前节点 if(j>r)Merge[deep][k++]=Merge[deep+1][i++]; else if(i>mid||Merge[deep+1][i]>Merge[deep+1][j])Merge[deep][k++]=Merge[deep+1][j++]; else Merge[deep][k++]=Merge[deep+1][i++]; } } -
查询(原序列的一个区间中比某个数小的有多少个数)
和线段树的区间查询差不多
如果当前区间完全被所求区间包含,则直接二分查找出有多少个数比所给的数小
否则,进入子节点查找,统计在两个子节点查找情况的和例子:在上面的归并树中查找在[2,5],有多少个数比4要小
- 首先,[1,5]不被[2,5]包含,进入子节点

- 然后[4,5]被[2,5]包含,二分查找返回1,[1,3]不完全被[2,5]包含,继续进入子节点<

- [3,3]被[2,5]包含,返回1,[1,2]不完全被[2,5]包含,继续进入子节点

- [1,1]不被[2,5]包含,[2,2]被[2,5]包含,返回0,最后结果是2

代码实现
int calc(int deep,int L,int R,int l,int r,int x){//计算[L,R]交[l,r]中小于x的有多少个数 if(L>=l&&R<=r){//[L,R]完全被[l,r]包含,直接二分查找返回 return lower_bound(Merge[deep]+L,Merge[deep]+R+1,x)-Merge[deep]-L; } int mid=(L+R)>>1,ans=0;//否则到子节点查找 if(mid>=l)ans+=calc(deep+1,L,mid,l,r,x); if(mid<r)ans+=calc(deep+1,mid+1,R,l,r,x); return ans; } - 首先,[1,5]不被[2,5]包含,进入子节点
区间第k大值查询
二分最终排好序的序列中的值,并且在[l,r]中查找有多少个数比它小,取答案为k-1的最大的数即可
int query(int l,int r,int k){
int L=1,R=n;
while(L<=R){//二分查找答案
int mid=(L+R)>>1,cnt;
cnt=calc(0,1,n,l,r,Merge[0][mid]);
if(cnt<=k)L=mid+1;//<mid的肯定不是答案
else R=mid-1;//>=mid的肯定不是答案
}
return Merge[0][L-1];//L不是答案(R不是答案,L>=R),L-2也不是答案(L=mid+1,mid-1不是答案),答案肯定是L-1
}
#include<cstdio>
#include<algorithm>
using namespace std;
#define maxn 100005
#define maxd 20
int n,m,a[maxn],Merge[maxd][maxn];
void build(int deep,int l,int r){//建树
if(l==r){Merge[deep][l]=a[l];return;}//叶子节点
int mid=(l+r)>>1;
build(deep+1,l,mid),build(deep+1,mid+1,r);//先构造子节点
for(int i=l,j=mid+1,k=l;i<=mid||j<=r;){//归并排序构造当前节点
if(j>r)Merge[deep][k++]=Merge[deep+1][i++];
else if(i>mid||Merge[deep+1][i]>Merge[deep+1][j])Merge[deep][k++]=Merge[deep+1][j++];
else Merge[deep][k++]=Merge[deep+1][i++];
}
}
int calc(int deep,int L,int R,int l,int r,int x){//计算[L,R]交[l,r]中小于x的有多少个数
if(L>=l&&R<=r){//[L,R]完全被[l,r]包含,直接二分查找返回
return lower_bound(Merge[deep]+L,Merge[deep]+R+1,x)-Merge[deep]-L;
}
int mid=(L+R)>>1,ans=0;//否则到子节点查找
if(mid>=l)ans+=calc(deep+1,L,mid,l,r,x);
if(mid<r)ans+=calc(deep+1,mid+1,R,l,r,x);
return ans;
}
int query(int l,int r,int k){
int L=1,R=n;
while(L<=R){//二分查找答案
int mid=(L+R)>>1,cnt;
cnt=calc(0,1,n,l,r,Merge[0][mid]);
if(cnt<=k)L=mid+1;//<mid的肯定不是答案
else R=mid-1;//>=mid的肯定不是答案
}
return Merge[0][L-1];//L不是答案(R不是答案,L>=R),L-2也不是答案(L=mid+1,mid-1不是答案),答案肯定是L-1
}
void work(){
for(int i=1;i<=n;i++)scanf("%d",a+i);
build(0,1,n);
int l,r,k;
for(int i=0;i<m;i++){
scanf("%d%d%d",&l,&r,&k);
printf("%d\n",query(l,r,k-1));
}
}
int main(){
while(~scanf("%d%d",&n,&m))work();
return 0;
}
来源:https://www.cnblogs.com/bennettz/p/8342242.html



