zoukankan      html  css  js  c++  java
  • 离散化,动态开点,历史版本,主席树p2086

      

     

      听名字非常的厉害了,它是线段树的高级应用.

      考虑如果你需要很多很多线段树的话,你需要开很多很多空间.但是如果这些线段树之间非常相似,我们可以把这一个线段树和上一个线段树共用大部分的节点.

      比如一个对与一个数组建立主席树,一个个的把元素放进线段树的话当然可以把重新做一个线段树,但每次只有logn个节点遭到更新或者新建,可以考虑如何压缩空间.

      嗯我只理解到了这里,以后继续更新

      先用三行离散化一下,处理出总的大小不同的数的数量sum

      

      o是原数组,a是用来离散化的.sort一下后a固然会变成有序的,但是unique这句话出来把a内从a[1]到a[n]值不同的数量赋值给sum外还会把这些数值不同的数挑一个放到前面,后面放的不知道是啥.这样子每次查找一个数是第几大的话就可以直接写 k=lower_bound(a+1,a+1+sum,要查的数)-a-1;

      然后先用tot++新建节点的方式新建一个树,没有要维护的信息,只是先把左右节点弄一下.设rt[]为最大的根节点的位置,lc[]为左端点,rc[]为右端点,ll[]表示管得到的最小的数(对应离散化之后的数),rr[]表示管得到的最小的数.这里举个栗子:

      对于n=15的这个数组,显然只有十个本质不同的树.拿他来建树的话就是这样的一个结构:

      (哎呀这个紫色也太好看了)

      而普通(正常)的线段树

      如果把树画出来其实可以发现是一个dfs序,这是和普通的线段树不一样的地方.虽然看起来少了一些点,但是它要多开两个数组记录儿子节点的位置,但普通线段树左右儿子一定是now*2与now*2+1所以不用记录,也不知道谁比较好.但是这个时候比没啥必要,人家主席树还要logn的空间记录以后的信息了.

       纪念一下调试代码:

    int i;
    int a[10000],n,m;
    int tot,rt[100],lc[100],rc[100],ll[100],rr[100];
    //rt=root lc左儿子 rc右耳子
    void build(int &t,int l,int r){
        t=++tot;
        ll[t]=l;
        rr[t]=r;
        if(l==r){
            ll[t]=l;
            rr[t]=r;
            return ;
        }
        int mid=(l+r)/2;
        build(lc[t],l,mid);
        build(rc[t],mid+1,r);
        return ;
    }
    void put(int hh){
        if(hh/10)
            cout<<hh;
        else
            cout<<' '<<hh;
        cout<<' ' ;
        return ;
    }
    int main(){
        freopen("123.in","r",stdin);
        freopen("123.out","w",stdout);
        n=read();
        for(i=1;i<=n;i++)
            a[i]=read();
        sort(a+1,a+1+n);
        int sum=unique(a+1,a+1+n)-a-1;
        cout<<sum<<endl;
        build(rt[0],1,sum);
        cout<<"rt  ";
        for(i=0;i<=sum;i++)
            put(rt[i]);
        cout<<endl;
        cout<<"下标";
        for(i=0;i<=tot;i++)
            put(i);
        cout<<endl;
        cout<<"lc  ";
        for(i=0;i<=tot;i++)
            put(lc[i]);
        cout<<endl;
        cout<<"rc  ";
        for(i=0;i<=tot;i++)
            put(rc[i]);
        cout<<endl;
        cout<<"ll  ";
        for(i=0;i<=tot;i++)
            put(ll[i]);
        cout<<endl;
        cout<<"rr  ";
        for(i=0;i<=tot;i++)
            put(rr[i]);
        return 0;
    }
    调试

     

      然后考虑把各个数插入线段树并形成新的版本.和单点修改一样,它也只改变logn次即管自己的那几个节点,我们可以把新的节点记录一下,从新的根节点出发,一定先新建一个节点,把自己的管的范围内的数的数量sum++,如果插入的数归左儿子管就去更新左儿子,否则去更新右儿子.如何更新呢?这是一个递归的过程:把这个儿子复制成一个新节点,更新sum与左右儿子.   

      每个点被logn个点管着,时间复杂度是n*logn,每次新增logn个点,空间多了一倍而已.

      然后就是查询如何做了.查询区间第k大的本质仍是查询区间内区间数的数量,又是一个递归的过程.

      考虑区间[1,y]的[l,r]查询就与正常的线段树查询一样,只不过进去的时候由根节点=1变为rt[y].查询[x,y]的话就需要用一下小小容斥[1,y][l,r]-[1,x-1][l,r].然后怎么查第k大呢?考虑同时查看两个地方.整个的主席树等价于n个线段树,每个线段树维护的是只有[1,i]这个范围的数时区间[l,r]的sum.要查询根节点为x,y的第k大(这是两个线段树),对于当前的区间[l,r]可以求出sum[l,mid],如果它大于k说明第k小应该在区间[l,r]里,我们递归进入lc[x],lc[y]管这的子树中,他们管的区间是[l,mid],继续查找第k大;否则应该去rc[x],rc[y]子树中,查找[mid+1,r]中的k-sum[l,mid]大值.最后当l==r时返回下标,它是在离散化数组中的位置.

      

      

      最后给出总的代码.

    int i,tx,ty,k;
    int a[200010],o[200010];
    int rt[200010],lc[200010<<5],rc[200010<<5],c[200010<<5],tot;
    int n,m,sum;
    void build(int &now,int l,int r){
        tot++;
        now=tot;
        if(l==r)
            return ;
        int mid=(l+r)>>1;
        build(lc[now],l,mid);
        build(rc[now],mid+1,r);
        return ;
    }
    int built(int now,int l,int r){
        int t=++tot;
        lc[t]=lc[now];rc[t]=rc[now];c[t]=c[now]+1;
        if(l==r)
            return t;
        int mid=(l+r)>>1;
        if(k<=mid)lc[t]=built(lc[t],l,mid);
        else      rc[t]=built(rc[t],mid+1,r);
        return t;
    }
    int ask(int x,int y,int l,int r,int k){
        if(l==r)
            return l;
        int mid=(l+r)>>1,tt=c[lc[y]]-c[lc[x]];
        if(tt>=k) return ask(lc[x],lc[y],l,mid,k);
        else      return ask(rc[x],rc[y],mid+1,r,k-tt);
    }
    int main(){
        n=read();m=read();
        for(i=1;i<=n;i++)
            a[i]=o[i]=read();
        sort(a+1,a+1+n);
        sum=unique(a+1,a+1+n)-a-1;
        build(rt[0],1,sum);
        for(i=1;i<=n;i++){
            k=lower_bound(a+1,a+1+sum,o[i])-a;
            rt[i]=built(rt[i-1],1,sum);
        }
        for(;m;m--){
            tx=read();ty=read();k=read();
            write(a[ask(rt[tx-1],rt[ty],1,sum,k)]);
            putchar(10);
        }
        return 0;
    }
    主席树
  • 相关阅读:
    Maven下载依赖项的源代码(source code)和Javadoc
    Spring读写xml文件
    重建二叉树
    从尾到头打印链表
    替换空格
    洞穴逃生
    二维数组中的查找
    分苹果
    最小生成树算法prim and kruskal
    HTTP报文格式详解
  • 原文地址:https://www.cnblogs.com/qywyt/p/10206863.html
Copyright © 2011-2022 走看看