P2617 Dynamic Rankings(动态区间第k大——树状数组套主席树)详解

要讲清楚这个问题还是需要从树状数组的原理去入手。

当时我没想明白的是为什么和静态主席树的update开点有不一样的地方,想来原因是这样的。


首先,根据网上的一些讲法和luogu的实际过题,个人认为树状数组套主席树的空间复杂度是((n+m)*logn*log(len))的,那么这题大约是17*17*2e5,但是实际上我开128倍就过了.所以还是觉得很迷。

先来复习树状数组:给你一个数列,边修改边询问,多次询问区间和,怎么做?

树状数组的内容就直接拉了:https://blog.csdn.net/Small_Orange_glory/article/details/81290634?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control


区间查询

ok 下面利用C[i]数组,求A数组中前i项的和 

举个例子 i=7;

sum[7]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7] ;   前i项和

C[4]=A[1]+A[2]+A[3]+A[4];   C[6]=A[5]+A[6];   C[7]=A[7];

可以推出:   sum[7]=C[4]+C[6]+C[7];

序号写为二进制: sum[(111)]=C[(100)]+C[(110)]+C[(111)];

 

再举个例子 i=5

sum[5]=A[1]+A[2]+A[3]+A[4]+A[5] ;   前i项和

C[4]=A[1]+A[2]+A[3]+A[4];   C[5]=A[5];

可以推出:   sum[5]=C[4]+C[5];

序号写为二进制: sum[(101)]=C[(100)]+C[(101)];

 


当我们修改A[]数组中的某一个值时  应当如何更新C[]数组呢?

回想一下 区间查询的过程,再看一下上文中列出的图

 

结合代码分析

  1. void add(int x,int y)
  2. {
  3. for(int i=x;i<=n;i+=lowbit(i))
  4. tree[i]+=y;
  5. }
  6. //可以发现 更新过程是查询过程的逆过程
  7. //由叶子结点向上更新C[]数组

 

如图: 

当更新A[1]时  需要向上更新C[1] ,C[2],C[4],C[8]

                     C[1],   C[2],    C[4],     C[8]

写为二进制  C[(001)],C[(010)],C[(100)],C[(1000)]

                                      1(001)        C[1]+=A[1]

lowbit(1)=001 1+lowbit(1)=2(010)     C[2]+=A[1]

lowbit(2)=010 2+lowbit(2)=4(100)     C[4]+=A[1]

lowbit(4)=100 4+lowbit(4)=8(1000)   C[8]+=A[1]


通过类似二进制的方式,达到了logn修改和logn动态查询前缀和的目的。

那么回到这题,我们把一颗主席树,看成是一个点,也就是图中树状数组的最底层。

那么我们通过这个方式,就可以优化修改logn棵与要修改的位置有关的C[],不过同时最后求和的时候多付出logn的代码[但是相当划算]


特别要注意的是update的过程,在静态主席树查询的过程中,代码是这样的:每次可以新开一颗主席树,不过更新的是和前一棵有区别的logn长链,剩下的直接指回去就好了。

同时,每一次循环中都会更新,所以这样是没问题的。

但是,在本题的动态单点修改中,就有问题了。不能再这么修改。

LL update(LL pre,LL l,LL r,LL x){
    LL rt=++tot;
    tree[rt].l=tree[pre].l;
    tree[rt].r=tree[pre].r;
    tree[rt].sum=tree[pre].sum+1;
    LL mid=(l+r)>>1;
    if(l<r){
        if(x<=mid){
            tree[rt].l=update(tree[pre].l,l,mid,x);
        }
        else tree[rt].r=update(tree[pre].r,mid+1,r,x);
    }
    return rt;
}
  for(LL i=1;i<=n;i++){
    LL x=lower_bound(b+1,b+1+siz,a[i])-b;
    root[i]=update(root[i-1],1,siz,x);
  }

还是以这张图为例,我们开始的时候更新pos=1的位置,在c[1],c[2],c[4],c[8]建立主席树,当然没什么问题。每颗树的左右儿子节点对应也没问题。

但是,当更新pos=2的位置的时候,问题出现了。

更新pos=2的时候,要修改c[2],c[4].c[8]的主席树,这时候我们不能再开新的节点去覆盖,这也是和静态的不同的地方。

LL modify(LL pre,LL l,LL r,LL x,LL val){
    LL rt=++tot;
    tree[rt].l=tree[pre].l;tree[rt].r=tree[pre].r;
    tree[rt].sum+=val;
    LL mid=(l+r)>>1;
    if(l<r){
        if(x<=mid) tree[rt].l=modify(tree[pre].l,l,mid,x,val);
        else tree[rt].r=modify(tree[pre].r,mid+1,r,x,val);
    }
    return rt;
}

如果仍要去这样开点的话,就会导致c[2],c[4],c[8]开成一颗新的主席树。而我们想要的是在c[2],c[4],c[8]维护加上去的信息,也就是更新原来在那的主席树的信息。

所以要改成引用,同时判断当前点是否开过,开过就代表这里是进行更新,而不是开点。

void modify(LL &now,LL l,LL r,LL x,LL val){
    if(!now) now=++tot;
    tree[now].sum+=val;
    if(l==r) return;
    LL mid=(l+r)>>1;
    if(x<=mid) modify(tree[now].l,l,mid,x,val);
    else modify(tree[now].r,mid+1,r,x,val);
}

剩下的参考网上的其他的博客就比较清楚了。这个博客还挺图文并茂的:https://www.cnblogs.com/TaylorSwift13/p/11228276.html 


#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
#define lowbit(x) x&(-x)
using namespace std;
const int maxn=1e5+100;
typedef long long LL;
struct Tree{
    LL l,r,rt,sum;
}tree[maxn*4*35];
struct operation{bool b;LL l,r,k;LL pos,t;}q[maxn];
LL n,m,a[maxn],root[maxn],o[maxn*2],tot=0,len=0;
LL temp[2][30],cnt[10];
void modify(LL &now,LL l,LL r,LL x,LL val){
    if(!now) now=++tot;
    tree[now].sum+=val;
    if(l==r) return;
    LL mid=(l+r)>>1;
    if(x<=mid) modify(tree[now].l,l,mid,x,val);
    else modify(tree[now].r,mid+1,r,x,val);
}
void pre_modify(LL pos,LL val){
    LL x=lower_bound(o+1,o+1+len,a[pos])-o;
    for(LL i=pos;i<=n;i+=lowbit(i)){
        modify(root[i],1,len,x,val);///处理出需要修改哪log棵主席树
    }
}
LL query(LL l,LL r,LL k){
    if(l==r) return l;
    LL mid=(l+r)>>1;LL sum=0;
    for(LL i=1;i<=cnt[1];i++) sum+=tree[tree[temp[1][i]].l].sum;
    for(LL i=1;i<=cnt[0];i++) sum-=tree[tree[temp[0][i]].l].sum;
    if(k<=sum){
        for(LL i=1;i<=cnt[1];i++){
            temp[1][i]=tree[temp[1][i]].l;
        }
        for(LL i=1;i<=cnt[0];i++){
            temp[0][i]=tree[temp[0][i]].l;
        }
        return query(l,mid,k);
    }
    else{
        for(LL i=1;i<=cnt[1];i++){
            temp[1][i]=tree[temp[1][i]].r;
        }
        for(LL i=1;i<=cnt[0];i++){
            temp[0][i]=tree[temp[0][i]].r;
        }
        return query(mid+1,r,k-sum);
    }
}
LL pre_query(LL l,LL r,LL k){
    memset(temp,0,sizeof(temp));
    cnt[0]=cnt[1]=0;
    for(LL i=r;i;i-=lowbit(i)) temp[1][++cnt[1]]=root[i];
    for(LL i=l-1;i;i-=lowbit(i)) temp[0][++cnt[0]]=root[i];
    return query(1,len,k);
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  cin>>n>>m;
  for(LL i=1;i<=n;i++){
    cin>>a[i];
    o[++len]=a[i];
  }
  for(LL i=1;i<=m;i++){
    char op;cin>>op;
    if(op=='Q'){
        q[i].b=1; cin>>q[i].l>>q[i].r>>q[i].k;
    }
    else{
        q[i].b=0;
        cin>>q[i].pos>>q[i].t;
        o[++len]=q[i].t;
    }
  }
  sort(o+1,o+len+1);
  len=unique(o+1,o+len+1)-o-1;
  for(LL i=1;i<=n;i++){
    pre_modify(i,1);
  }
  for(LL i=1;i<=m;i++){
    if(q[i].b==1){
        cout<<o[pre_query(q[i].l,q[i].r,q[i].k)]<<endl;
    }
    else{
        pre_modify(q[i].pos,-1);
        a[q[i].pos]=q[i].t;
        pre_modify(q[i].pos,1);
    }
  }
return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值