Dynamic Rankings,洛谷P2617,树状数组+主席树

正题

      这道题我理解了半天,网上好的题解我没有看懂,所以在这里尽量写得详细简略一些。

      动态第k大要了解的是两个东西。

      一个是树状数组的概念,一个是主席树(动态开点线段树)。

     先讲树状数组;

     1.定义一个点i维护的信息是[i-lowbit(i)+1,i].lowbit()这个函数的意义是i在二进制下末尾零和倒数第一个数组成的数。像lowbit(7) = lowbit( (111)2 ) = (1)2 = 1 。2指的是二进制下。

     2.那么如何扯到这个题上面去呢?

     再讲主席树。

     1.在求静态第k大的过程中,我们习惯于用前缀和主席树来处理区间问题,然后利用“每次只加一条链”的性质节省空间。

     2.但是在此题中,如果还是用根来表示位置,用底层主席树维护的值来表示权值的话,那么修改必将很麻烦。

     3.因为在这时,更改当前点x的值是会影响到x~n的主席树的构成。

      所以我们在这里提出——用树状数组套一套

      我们现在建出来的主席树,第i个根维护的是[i-lowbit(i)+1,i]区间的信息,而不是像以前的前缀树一样。但是很多人想问,前缀树有自己优化空间的方法,树状数组套主席树有自己的优化方法吗?

      没有

      所以我们要暴力开空间

void update(int &now,int l,int r){//这里的now是引用,表示上一层的左儿子或右儿子
    if(now==0) now=++tot;//我还没有这个儿子,新建出来
    c[now]+=d;//加上一个d,d在增加的时候为1,减的时候为0
    if(l==r) return ;
    if(v<=(l+r)/2) update(ls[now],l,(l+r)/2);//在左边,往左边找
    else update(rs[now],(l+r)/2+1,r);//在右边,往右边找
}

       代码理解好了,什么都没有事。

      最后要看的还是全部。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;

int a[10010];
int n,m;
int root[10010],ls[2600010],rs[2600010],c[2600010];
int tot=0;
int xx[30],yy[30];
int v,d;

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

void update(int &now,int l,int r){
    if(now==0) now=++tot;
    c[now]+=d;
    if(l==r) return ;
    if(v<=(l+r)/2) update(ls[now],l,(l+r)/2);
    else update(rs[now],(l+r)/2+1,r);
}

void change(){
    int x,b;
    scanf("%d %d",&x,&b);
    d=-1;v=a[x];
    for(int i=x;i<=n;i+=lowbit(i))//把原来的删掉
        update(root[i],0,1e9);
    d=1;v=b;
    for(int i=x;i<=n;i+=lowbit(i))//把新的加上
        update(root[i],0,1e9);
    a[x]=b;
}

int query(){
    int x,y,k;
    scanf("%d %d %d",&x,&y,&k);
    x--;
    swap(x,y);
    int t1=0,t2=0;
    for(int i=x;i>=1;i-=lowbit(i)) xx[++t1]=root[i];//记住,这里是log个根一起跑
    for(int i=y;i>=1;i-=lowbit(i)) yy[++t2]=root[i];
    int l=0,r=1e9;//二分模拟边界变化
    while(l<r){
        int temp=0;//算出左儿子
        for(int i=1;i<=t1;i++) temp+=c[ls[xx[i]]];
        for(int i=1;i<=t2;i++) temp-=c[ls[yy[i]]];
        if(k<=temp){//在左儿子,往左走,全部往左边跳
            for(int i=1;i<=t1;i++) xx[i]=ls[xx[i]];
            for(int i=1;i<=t2;i++) yy[i]=ls[yy[i]];
            r=(l+r)/2;
        }
        else {//在右边,全部往右边跳
            for(int i=1;i<=t1;i++) xx[i]=rs[xx[i]];
            for(int i=1;i<=t2;i++) yy[i]=rs[yy[i]];
            k-=temp;l=(l+r)/2+1;//记住在右边找的是第k-temp个
        }
    }
    return l;
}

int main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&v);
        a[i]=v;d=1;
        for(int j=i;j<=n;j+=lowbit(j))//对于每个值,加入时要像树状数组一样跳
            update(root[j],0,1e9);
    }
    char t[2];
    while(m--){
        scanf("%s",t);
        if(t[0]=='Q') printf("%d\n",query());
        else change();
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值