zoukankan      html  css  js  c++  java
  • 【线段树】树套树 树状数组套主席树

    树状数组套主席树

    (其实是树状数组套动态开点权值线段树

    作用:

    即动态主席树,动态(带修)查询区间 (k) 小(大)值

    做法:

    回顾主席树:简单来说主席树在查询时 是查询 (T[r]-T[l-1]) 区间的值的,建立是 (T[i+1])(T[i]) 的基础上建立的。用于静态查询区间 (k) 小(大)值。

    而带修主席树,因为修改,所以在 (T[i]) 基础上建 (T[i+1]) 并不合适。

    主席树的思想是减去区间的前缀和求差,此时带修主席树依旧是考虑维护前缀和,然而因为带修,所以主席树各版本之间的强关联性并不适合修改,此时再建立主席树不再需要对线段树进行可持久化,只需动态开点建权值线段树。

    考虑用树状数组维护前缀和,及对树状数组每个 (lowbit) 的点( (logn) 个)建立动态开点权值线段树,

    在修改数组 (x) 位置时,则是对树状数组 (for( ;x<=n;x+=lowbit(x))) 上各点的权值线段树进行修改;

    在查询数组区间 ([l,r])(k) 小(大)值时,则是对树状数组 (for(i=r;i;i-=lowbit(i))) 以及 (for(i=l-1;i;i-=lowbit(i))) 在线段树当前区间的相应各点上的值差 进行线段树的二分查询(详见模板)。

    例题:

    luoguP2617

    模板题,带修查询区间k小值:

    给定一个含有 (n) 个数的序列 (a_1,a_2 dots a_n),有 (m) 个操作,需要支持两种操作:

    • Q l r k 表示查询下标在区间 ([l,r]) 中的第 (k) 小的数
    • C x y 表示将 (a_x) 改为 (y)

    (1le n,m le 10^5)(1 le l le r le n)(1 le k le r-l+1)(1le x le n)(0 le a_i,y le 10^9)

    (code:)

    #include<bits/stdc++.h>
    #define mem(a,b) memset(a,b,sizeof(a))
    using namespace std;
    typedef long long ll;
    const int mod=1e9+9;
    const int inf=0x3f3f3f3f;
    const int maxn=2e5+5;
    const double ep=1e-6;
    
    void read(int&x)
    {
        char c;
        while(!isdigit(c=getchar()));x=c-'0';
        while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-'0';
    }
    
    //P2617
    int n,cnt;
    int h[maxn];
    inline int id(int x){
        return lower_bound(h+1,h+1+cnt,x)-h;
    }
    struct Q{
        int a,b,c;
    }q[maxn];
    //树状数组套主席树(其实是套动态开点权值线段树
    //对树状数组每个节点建权值线段树
    struct Tr{
        // <<8 是因为log*log 
        // maxn=2e5是因为权值包括数组的n个权值 以及q的m个权值 maxn=n+m
        int T[maxn],L[maxn<<8],R[maxn<<8],sum[maxn<<8],tot=0;
        void supdate(int &rt,int l,int r,int x,int v)//线段树
        {
            if(!rt)rt=++tot;
            sum[rt]+=v;
            if(l==r)return;
            int mid=(l+r)>>1;
            if(x<=mid)supdate(L[rt],l,mid,x,v);
            else supdate(R[rt],mid+1,r,x,v);
        }
        void update(int x,int vx,int v)//树状数组 x是数组位置,vx是权值位置 v是+1 ,-1这样子
        {
            for(;x<=n;x+=x&-x)supdate(T[x],1,cnt,vx,v);
        }
        //求区间第k小
        int qr[maxn],ql[maxn],cntl,cntr;
        void pre_query(int x,int q[],int&cnt){
            for(cnt=0;x;x-=x&-x)q[++cnt]=T[x];
        }
        //l,r是二分线权值段树 k是区间第k小的k
        //l和r是权值区间,数组区间是在树状数组上分的 就是lowbit部分那里处理判断数组区间
        int query(int l,int r,int k)//qury(T[qR],T[qL-1],l,r,k) 提前记录log个节点T[qR]及T[qL-1]在q[]处
        {
            if(l==r)return l;
            int mid=(l+r)>>1,num=0;
            for(int i=1;i<=cntr;i++)num+=sum[L[qr[i]]];
            for(int i=1;i<=cntl;i++)num-=sum[L[ql[i]]];//其实就是num=L[rt]-L[pre]
            if(num>=k)
            {
                for(int i=1;i<=cntr;i++)qr[i]=L[qr[i]];
                for(int i=1;i<=cntl;i++)ql[i]=L[ql[i]];
                return query(l,mid,k);//相当于 query(L[rt],L[pre],l,mid,k); 因为要记录树状数组节点 所以得提前记录
            }else{
                for(int i=1;i<=cntr;i++)qr[i]=R[qr[i]];
                for(int i=1;i<=cntl;i++)ql[i]=R[ql[i]];
                return query(mid+1,r,k-num);//query(R[rt],R[pre],mid+1,r,k-num);
            }
        }
    }tr;
    int a[maxn];
    char op[2];
    int main()
    {
        int m;
        read(n);read(m);
        for(int i=1;i<=n;i++)read(a[i]),h[++cnt]=a[i];
        int l,r,k,x,y;
        for(int i=1;i<=m;i++)
        {
            scanf("%s",op);
            if(op[0]=='Q'){
                read(l);read(r);read(k);
                q[i]={l,r,k};
            }else{
                read(x);read(y);h[++cnt]=y;
                q[i]={x,y,0};
            }
        }
        sort(h+1,h+1+cnt);
        cnt=unique(h+1,h+1+cnt)-h-1;
        for(int i=1;i<=n;i++)tr.update(i,id(a[i]),1);
        for(int i=1;i<=m;i++)
        {
            if(q[i].c){
                l=q[i].a;r=q[i].b;k=q[i].c;
                tr.pre_query(r,tr.qr,tr.cntr);
                tr.pre_query(l-1,tr.ql,tr.cntl);
                printf("%d
    ",h[tr.query(1,cnt,k)]);
            }else{
                x=q[i].a;y=q[i].b;
                tr.update(x,id(a[x]),-1);a[x]=y;
                tr.update(x,id(a[x]),1);
            }
        }
    }
    
  • 相关阅读:
    磁盘分区异常占用满了
    平滑升级nginx
    supervisor进程异常挂掉
    datetime值毫秒四舍五入
    docker+tomcat 启动时非常慢原因之JRE /dev/random阻塞
    Tomcat最大连接数问题
    Docker:设置代理proxy
    easy_install和pip安装python库修改默认的源
    zabbix监控mysql之Warning: Using a password on the command line interface can be insecure.
    Mysql忘记密码解决方法
  • 原文地址:https://www.cnblogs.com/kkkek/p/13637166.html
Copyright © 2011-2022 走看看