动态主席树

写在前面

相对于静态主席树,动态主席树意味着,每个节点可能会修改称为新的值,按照我们平时的想法,一定是在之前要删除的位置上进行-1操作,然后再新插入的位置上+1

解析

静态主席树我们在每个节点处创建一个权值线段树,然后在区间[l,r]求解时,使用第r棵线段树 - 第l-1棵线段树,在这个线段树上进行查询第k大元素的离散位置,然后根据之前进行离散的结果,得到最终的位置

使用ZOJ-2112作为栗子,数据为:3 2 1 4 7 6

这里要把所有的数据,包括在下面需要进行修改的数据,一次性全部列举出来,然后进行离散操作,因为主席树是一种离线结构,在起始要将所有数据都列举出来,为其开辟空间,然后再进行操作,这就是主席树的局限

那么离散化之后的数据为:3 2 1 4 6 5 

同静态树状数组相同,使用数组T表示线段树的根节点

对于动态主席树,我们需要增加一些另外的线段树,这些线段树根节点假设用数组S表示,这些线段树只是用来维护修改节点的信息,那么我们要查询区间问题,怎么维护这样一个S数组呢?对于区间维护,树状数组是一个很好的选择

开始,我们的数组长度为5,那么我们就可以只维护S[1] ~S[5],因为区间查询和修改的范围就是这些

当出现修改时,如:C 2 6 (将第二个位置上的数字改为6) 开始a[2] = 2,对应离散后位置2,那么就要在到达2位置的整条路径上进行-1操作,6对应离散后位置为5,那么就要在到达5位置的整条路径上进行+1操作,同时使用树状数组维护,需要同时更新相对应的树

在进行求解的时候,数组T对应的,使用和静态主席树相同的方法,对于S数组,需要使用树状数组区间求和的思想

程序

#include <iostream>
#include <algorithm>
#include <cstdio>

using namespace std;

const int maxn = 6e4+5;
const int maxm = 1e4+5;
//S[i]表示树状数组维护的第i棵线段树的根节点的编号
int T[maxn],S[maxn],L[maxn*32],R[maxn*32],sum[maxn*32];    //T表示每个节点所产生的权值线段树    L某个节点的左儿子  R右儿子   sum节点的权值
int sz[maxn],h[maxn];                     //h数组用来进行离散化操作
int ul[maxn],ur[maxn];
int tot,num,n,q;                          //tot为从0开始对于节点的编号

struct Query{
    int l,r,k;
    bool flag; //ture代表Q,false代表C
}Q[maxm];  //存储询问

void build(int& rt,int l,int r)                           //建空树
{
    rt = ++tot;
    sum[rt]=0;
    if(l==r) return;
    int mid = (l+r)>>1;
    build(L[rt],l,mid);
    build(R[rt],mid+1,r);
}

void update(int& rt,int pre,int l,int r,int x,int val)      //x表示离散化后插入节点的位置
{
    rt = ++tot;
    L[rt] = L[pre];
    R[rt] = R[pre];
    sum[rt] = sum[pre]+val;
    if(l==r) return;
    int mid = (l+r)>>1;
    if(x<=mid) update(L[rt],L[pre],l,mid,x,val);            //更新L[rt]节点
    else update(R[rt],R[pre],mid+1,r,x,val);                //更新R[rt]节点
}

int lowbit(int x)
{
    return x&(-x);
}

void add(int x,int val)
{
    int res = lower_bound(h+1,h+1+num,sz[x]) - h;           //表示离散化之后的位置
    while(x <= n)
    {
        update(S[x],S[x],1,num,res,val);                    //这里更新节点,不需要知道前一个节点数值,因为初始化时,每个S维护的线段树都是初始化的线段树
        x += lowbit(x);
    }
}

int Sum(int x,bool flag)                                   //权值线段树维护的区间求和
{
    int res=0;
    while(x>0)
    {
        if(flag) res += sum[L[ur[x]]];                      //当选择左子树时
        else res += sum[L[ul[x]]];
        x -= lowbit(x);
    }
    return res;
}

/*
s:查询区间开始前一个位置     e:查询区间结束位置
ts:s对应的跟节点位置         te:e对应跟节点位置
l、r:表示当前递归所在区间
k:查询区间的第k大元素
*/
int query(int s,int e,int ts,int te,int l,int r,int k)
{
    if(l==r) return l;
    int mid = (l+r)>>1;
    int res = Sum(e,true)-Sum(s,false)+sum[L[te]]-sum[L[ts]];
    if(k<=res)
    {
        for(int i=e;i;i-=lowbit(i)) ur[i] = L[ur[i]];
        for(int i=s;i;i-=lowbit(i)) ul[i] = L[ul[i]];
        return query(s,e,L[ts],L[te],l,mid,k);
    }
    else
    {
        for(int i=e;i;i-=lowbit(i)) ur[i] = R[ur[i]];
        for(int i=s;i;i-=lowbit(i)) ul[i] = R[ul[i]];
        return query(s,e,R[ts],R[te],mid+1,r,k-res);
    }
}


int main()
{
    //线段树为一种离线结构,必须预先知道数字的范围
    int t;
    scanf("%d",&t);
    while(t--)
    {
        char str[5];
        num=0;
        scanf("%d%d",&n,&q);
        for(int i=1;i<=n;i++) scanf("%d",sz+i),h[++num]=sz[i];
        for(int i=1;i<=q;i++)
        {
            scanf("%s",str);
            if(str[0]=='Q')
            {
                scanf("%d%d%d",&Q[i].l,&Q[i].r,&Q[i].k);
                Q[i].flag=true;
            }
            else
            {
                scanf("%d%d",&Q[i].l,&Q[i].r);
                Q[i].flag=false;
                h[++num]=Q[i].r;
            }
        }
        sort(h+1,h+1+num);
        int tmp = unique(h+1,h+1+num)-h-1;
        num = tmp;                                                                           //离散化操作
        tot=0;
        build(T[0],1,num);
        for(int i=1;i<=n;i++) update(T[i],T[i-1],1,num,lower_bound(h+1,h+1+num,sz[i])-h,1);
        for(int i=1;i<=n;i++) S[i] = T[0];                                                  //树状数组所维护的线段树开始都初始化为T[0]
        for(int i=1;i<=q;i++)
        {
            if(Q[i].flag)                                                                   //查询操作
            {
                //查询时,我们既要查询原数列的信息,又要查询修改信息,所以我们需要把树状数组需要查询的节点
                //全部储存到一个数组中,在进行查询,原序列的查询方式和静态主席树一样,树状数组的查询方式和
                //树状数组区间查询一样,只不过把每次访问节点改为这个节点代表的值域线段树,注意:每一次查询
                //所有有关信息的访问必须同步
                for(int j=Q[i].r;j;j-=lowbit(j)) ur[j] = S[j];                              //将右边节点查询所需要的查询的节点全部放在ur数组中
                for(int j=Q[i].l-1;j;j-=lowbit(j)) ul[j] = S[j];
                printf("%d\n",h[query(Q[i].l-1,Q[i].r,T[Q[i].l-1],T[Q[i].r],1,num,Q[i].k)]);
            }
            else
            {
                add(Q[i].l,-1);
                sz[Q[i].l] = Q[i].r;                                                       //将当前位置上的数字更新成后来的数值
                add(Q[i].l,1);                                                             //需要再进行一次add操作
            }
        }
    }
    return 0;
}

参考博客

动态主席树:https://blog.csdn.net/WilliamSun0122/article/details/77885781

静态主席树:https://blog.csdn.net/li1615882553/article/details/80941342

猜你喜欢

转载自blog.csdn.net/li1615882553/article/details/84374713