zoukankan      html  css  js  c++  java
  • 动态主席树(带修改的区间第k大)(树套树)

    动态主席树(带修改的区间第k大)(树套树)

    基本思想

    区间第k小的问题我们可以用静态主席树来维护,但是一些题目往往会增加修改操作,那么我们应该怎么做呢,先看例题。

    给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i+2]……a[j]中第k小的数是多少(1≤k≤j-i+1),并且,你可以改变一些a[i]的值,改变后,程序还能针对改变后的a继续回答上面的问题。你需要编一个这样的程序,从输入文件中读入序列a,然后读入一系列的指令,包括询问指令和修改指令。
    
    输入格式:
    第一行有两个正整数n(1≤n≤10000),m(1≤m≤10000)。分别表示序列的长度和指令的个数。
    第二行有n个数,表示a[1],a[2]……a[n],这些数都小于10^9。接下来的m行描述每条指令,每行的格式是下面两种格式中的一种。 Q i j k 或者 C i t
    Q i j k (i,j,k是数字,1≤i≤j≤n, 1≤k≤j-i+1)表示询问指令,询问a[i],a[i+1]……a[j]中第k小的数。
    C i t (1≤i≤n,0≤t≤10^9)表示把a[i]改变成为t。
    输出格式:
    对于每一次询问,你都需要输出他的答案,每一个输出占单独的一行。
    

    这道题如果只用静态主席树是不可能的,因为有修改操作,静态主席树是不能修改的,我们就需要能支持修改的动态主席树。
    我们想,单点修改,区间查询,这不是树状数组最擅长的吗。但是显然,树状数组是不能维护这个东西的,所以我们就需要套一个主席树去维护,用一颗主席树维护原序列的信息,再用主席树维护一个树状数组(其实这个算是树套树了)去维护修改操作的信息。

    实现方法

    首先还是离散化,但要注意,修改的值也需要离散化,因为修改的值和原值位置不同,所以这里我们选择用指针对地址进行操作

        void work(){
            sort(dis+1,dis+num+1,cmp);p[0]=-1;/*dis表示一个指针变量的数组,num表示需要离散的数据的数量*/
            for(int i=1,j=0;i<=num;++i)
            {
                if(*dis[i]!=p[j])p[++j]=*dis[i];
                *dis[i]=j;
            }
        }
    

    构建

    维护原序列的主席树跟静态主席树一样,直接复制过来也可以,但为了防止打挂,还是重打一遍更好。而维护树状数组的主席树其实也差不多

    修改

    用树状数组维护一个区间修改的信息,每一个节点的范围跟树状数组没有区别,但我们需要用主席树去维护这个树状数组,树状数组的每一个节点都是一颗值域线段树,保存树状树状每一个节点的根,每次修改就对树状数组包含这个元素的节点进行修改,每次修改都相当于删除一个元素再插入一个元素,每次维护都要基于这个节点的原本信息进行修改(+1或-1)。由于每次都要维护log个节点,每个节点要新增(log)个节点所以时空复杂度均为(log^2)
    建树和维护本质上都是基于一颗原线段树进行修改,所以可以使用同一个函数进行操作。

        int modify(int l,int r,int x,int k,int o){
            int y=++cnt;
            t[y]=t[x];t[y].x+=o;
            if(l==r)return y;
            int mid=(l+r)>>1;
            if(k<=mid)t[y].l=modify(l,mid,t[x].l,k,o);
            else t[y].r=modify(mid+1,r,t[x].r,k,o);
            return y;
        }
    

    查询

    查询时,我们既要查询原序列的信息,又要查询修改信息,所以我们需要把树状数组需要查询的节点全部储存到一个数组里,再进行查询,原序列的查询方式跟静态主席树一样,树状数组的查询就与树状数组的区间查询一样,只不过把每次访问节点改为这个节点代表的值域线段树,注意,每一次查询的所有有关信息的访问必须同时进行

    int query(int l,int r,int s1,int s2,int k){
        if(l==r)return l;
        int x=t[t[s2].l].x-t[t[s1].l].x;
        for(int i=1;i<=tot1;++i)x-=t[t[q1[i]].l].x;
        for(int i=1;i<=tot2;++i)x+=t[t[q2[i]].l].x;
        int mid=(l+r)>>1;
        if(x>=k)
        {
            for(int i=1;i<=tot1;++i)q1[i]=t[q1[i]].l;
            for(int i=1;i<=tot2;++i)q2[i]=t[q2[i]].l;
            return query(l,mid,t[s1].l,t[s2].l,k);
        }
        else
        {
            for(int i=1;i<=tot1;++i)q1[i]=t[q1[i]].r;
            for(int i=1;i<=tot2;++i)q2[i]=t[q2[i]].r;
            return query(mid+1,r,t[s1].r,t[s2].r,k-x);
        }
    }
    

    代码

    #include<bits/stdc++.h>
    using namespace std;
    inline int gi(){
        char a=getchar();int b=0;
        while(a<'0'||a>'9')a=getchar();
        while(a>='0'&&a<='9')b=b*10+a-'0',a=getchar();
        return b;
    }
    const int N=1e4+50;
    struct node  {int l,r,x;}  t[N*900];
    struct ppp  {int l,r,op,k;}  b[N];
    int cmp(int* x,int* y)  {return *x<*y;}
    int a[N],p[N*5],n,m,tot1,tot2,lshh,cnt=1,root[N],root1[N],q1[N],q2[N];   int *lsh[N*5];
    void work(){
        sort(lsh+1,lsh+lshh+1,cmp);p[0]=-1;
        for(int i=1,j=0;i<=lshh;++i)
        {
            if(*lsh[i]!=p[j])p[++j]=*lsh[i];
            *lsh[i]=j;
        }
    }
    int modify(int l,int r,int x,int k,int o){
        int y=++cnt;
        t[y]=t[x];t[y].x+=o;
        if(l==r)return y;
        int mid=(l+r)>>1;
        if(k<=mid)t[y].l=modify(l,mid,t[x].l,k,o);
        else t[y].r=modify(mid+1,r,t[x].r,k,o);
        return y;
    }
    int query(int l,int r,int s1,int s2,int k){
        if(l==r)return l;
        int x=t[t[s2].l].x-t[t[s1].l].x;
        for(int i=1;i<=tot1;++i)x-=t[t[q1[i]].l].x;
        for(int i=1;i<=tot2;++i)x+=t[t[q2[i]].l].x;
        int mid=(l+r)>>1;
        if(x>=k)
        {
            for(int i=1;i<=tot1;++i)q1[i]=t[q1[i]].l;
            for(int i=1;i<=tot2;++i)q2[i]=t[q2[i]].l;
            return query(l,mid,t[s1].l,t[s2].l,k);
        }
        else
        {
            for(int i=1;i<=tot1;++i)q1[i]=t[q1[i]].r;
            for(int i=1;i<=tot2;++i)q2[i]=t[q2[i]].r;
            return query(mid+1,r,t[s1].r,t[s2].r,k-x);
        }
    }
    int main(){
        cin>>n>>m;
        for(int i=1;i<=n;++i)
        {
            a[i]=gi();
            lsh[++lshh]=&a[i];
        }
        for(int i=1;i<=m;++i)
        {
            char aa=getchar();
            while(!(aa=='Q'||aa=='C'))aa=getchar();
            b[i].l=gi();
            b[i].r=gi();
            if(aa=='C')
            {
                b[i].op=1;
                lsh[++lshh]=&b[i].r;
            }
            else b[i].k=gi();
        }
        work();
        for(int i=1;i<=n;++i)
            root1[i]=root[1];
        for(int i=1;i<=n;++i)
            root[i]=modify(1,lshh,root[i-1],a[i],1);
        for(int i=1;i<=m;++i)
            if(b[i].op)
            {
                int x=b[i].l,y=b[i].r,s=a[x];a[x]=y;
                while(x<=n)
                {
                    root1[x]=modify(1,lshh,root1[x],s,-1);
                    root1[x]=modify(1,lshh,root1[x],y,1);
                    x+=(x&(-x));
                }
            }
            else
            {
                tot1=0,tot2=0;int x=b[i].l-1;
                while(x){q1[++tot1]=root1[x];x-=(x&(-x));}x=b[i].r;
                while(x){q2[++tot2]=root1[x];x-=(x&(-x));}
                printf("%d
    ",p[query(1,lshh,root[b[i].l-1],root[b[i].r],b[i].k)]);
            }
            return 0;
    }
    
  • 相关阅读:
    golang中的左值VS右值
    golang指针接收者和值接收者方法调用笔记
    go中如果想要实现别人写的接口,如何保证确实实现了那个接口而不是错过了什么?
    在windows中给git修改默认的编辑器为sublime
    git config 选项
    json包中的Marshal&Unmarshal 文档译本
    go的database/sql库中db.Exce()
    go中导入包的几种方式
    机器学习之分类和聚类的区别
    TP5.0学习笔记
  • 原文地址:https://www.cnblogs.com/ljq-despair/p/8639394.html
Copyright © 2011-2022 走看看