主席树(可持久化权值线段树)

这是一种用于处理一段数(e.g.1,2,3,4,5)在一个序列的某个区间[l,r]上出现几次/区间k小/查询历史版本的数据结构

前言:权值线段树

权值线段树可以维护某个区间内数组元素出现的次数。

1.主席树是什么?

对于一颗权值线段树,我们要往里面添加n个数字,我们知道,这很容易实现,只需要一个for循环,然后调用n次modify函数即可,但是如果需要一个记忆化的过程(可持久化),即我们每添加一个元素都需要记住这个权值线段树的状态,以便于我们对历史版本进行操作,那么如果只使用权值线段树,则我们需要n个权值线段树同时保存状态,这样做空间一定会爆炸,主席树则是解决这一问题的方法。

首先观察,每次更改(添加)一个数,线段树的变化
//图例
可以发现,对于每次添加,只会有一条以根节点为起点,代表被修改数字的叶子节点为终点的链有改动(长度为log n)因此,我们可以每次需改新建一条链,并将其与原树的某节点相连,形成新版本的线段树
时间复杂度O(NlogN)空间复杂度O(N+NlogN)

2.主席树实现

(1)建树先准备struct

与一般线段树不同,主席树每个节点不仅要记录权值还要记录左/右儿子的编号(因为每次要新建一条链)
用结构体存(数组要开logN倍以上)

(2)开始建树build

递归左右儿子,并记录编号

(3)要支持修改(添加一个数)modify

像线段树一样递归,每递归到一个节点,意味着它需要更新,复制一个新节点,向下递归,由于每个节点只有一个儿子与目标节点相交,所以新节点有一个儿子为原树上的节点,一个节点为新节点,向下递归并记录新儿子编号

(4)要支持查询 check

同线段树一样

3.典题精讲

静态区间第k小
建一个空树
对于每个\(a_i\)(小至大)进行一次修改操作形成n棵线段树
查询[l,r]区间第k小ans
已知ans属于[1,maxn]即根节点范围
令check(i,j)为已i为根(只含区间\(a_1~a_i\)的数)j号节点范围中数的数量
发明者的原话:“对于原序列的每一个前缀[1···i]建立出一棵线段树维护值域上每个数出现的次数,则其树是可减的”

可以加减的理由:主席树的每个节点保存的是一颗线段树,维护的区间信息,结构相同,因此具有可加减性

详细查询过程: 原始区间: [1,2,3] 查询 1到3 里的第二小的值(应该为2)

我们查询 l=1 r=3 k=2 因此这样查询:check(r,r) - query(l-1,l-1)(sum[r]-sum[l-1])
首先进入query函数,当前为12编号根节点,前一个为1编号根节点。对于编号12的根节点的左孩子编号为10,编号为1的根节点左孩子是2,让sum[10]-sum[2],sum记录的是此节点的val,相减得2,因为k=2,所以进入左子树递归。
当前为10编号根节点,前一个为2编号根节点。对于编号10的根节点的左孩子编号为8,编号为2的根节点左孩子是3,让d=(sum[8]-sum[3]),sum记录的是此节点的val,相减得1,因为k=2,所以进入右子树递递归,同时 k - d
当前为11编号根节点,前一个为4编号根节点。此时已经到达了叶子节点,返回 pl或者pr,得结果为 2(此时pl== pr==2,达到编号为4的叶子节点)

include<bits/stdc++.h>
using namespace std;
int a[200010],c[200010];  //c为离散化
int root[200010],top,n,m;
struct node{
    int ls,rs,val;
}tree[40*200010];
int build(int cur,int l,int r){
    cur=++top;
    tree[cur].val=0;
    if(l==r) return cur;
    int mid=(l+r)>>1;
    tree[cur].ls=build(cur,l,mid);
    tree[cur].rs=build(cur,mid+1,r);
//    cout<<cur<<' '<<l<<' '<<r<<" "<<tree[cur].val<<" "<<tree[cur].ls<<" "<<tree[cur].rs<<endl;
    return cur;
}
int clone(int pre){
    ++top;
    tree[top].ls=tree[pre].ls;
    tree[top].rs=tree[pre].rs;
    tree[top].val=tree[pre].val+1;
    return top;
}
int modify(int pre,int x,int l,int r){
    int cur=clone(pre);
    if(l==r) return cur;
    int mid=(l+r)>>1;
    if(x<=mid){
        tree[cur].ls=modify(tree[cur].ls,x,l,mid);
    }
    else{
        tree[cur].rs=modify(tree[cur].rs,x,mid+1,r);
    }
//    cout<<cur<<' '<<l<<' '<<r<<" "<<tree[cur].val<<" "<<tree[cur].ls<<" "<<tree[cur].rs<<endl;
    return cur;
}
int check(int cur,int pre,int l,int r,int x){
    int L1=tree[cur].ls;
    int L2=tree[pre].ls;
 //   cout<<cur<<' '<<pre<<" "<<L1<<" "<<L2<<endl;
    if(l==r) return l;
    int mid=(l+r)>>1;
    int num=tree[L1].val-tree[L2].val;
    if(num>=x){
        return check(tree[cur].ls,tree[pre].ls,l,mid,x);
    }
    else{
        return check(tree[cur].rs,tree[pre].rs,mid+1,r,x-num);  //x-num
    }
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i],c[i]=a[i];
    sort(c+1,c+n+1);
    int cnt=unique(c+1,c+n+1)-c-1;
    root[0]=build(0,1,cnt);
//    cout<<endl;
//    cout<<cnt;
    for(int i=1;i<=n;i++){
        int num=lower_bound(c+1,c+cnt+1,a[i])-c;
        root[i]=modify(root[i-1],num,1,cnt);
    }
    while(m--){
        int l,r,k;
        cin>>l>>r>>k;
        int ans=check(root[r],root[l-1],1,cnt,k);
        cout<<c[ans]<<endl;
    }
    return 0;
}
posted on 2024-08-02 22:50  Grylls_117  阅读(8)  评论(0编辑  收藏  举报