归并树

本文介绍了一种结合线段树和归并排序的数据结构——归并树。归并树能够高效地解决区间内元素的排序问题,如求解区间内比某数小的元素数量及区间第k大值等问题。文章提供了归并树的构建方法和查询算法,并通过POJ2104题目进行了实例演示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

定义

归并树是线段树和归并排序的合成,它利用线段树将归并排序的每一步都记录下来

例如我们对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;
    }

区间第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 
}

例题POJ2104 K-th Number

#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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值