权值线段树详解

简述

  权值线段树是线段树的一种,可以实现平衡树的基本操作,权值线段树的节点维护的是这个区间的数的出现次数,例如区间序列为1 2 2 3 3 3,那么该节点的值为6。权值线段是可以在logn的时间内求出全局的k小值,某数在全局的rank,一个数的前驱或者后继。因为是线段树的一种所以思想也是二分,其本质就是一个可以二分的桶。

代码详解

定义

因为是线段树的一种所以空间也开4n

typedef long long ll;
ll a[maxn<<2];

单点更新

单点更新的操作和普通线段树一样,递归到某个叶子节点操作即可

void update(int pos,int val,int l,int r,int rt){
    if(l==r){
        a[rt]+=val;
        return;
    }
    int m=l+r>>1;
    if(pos<=m)
        update(pos,val,l,m,rt<<1);
    else
        update(pos,val,m+1,r,rt<<1|1);
    pushup(rt);
}

第k小的值

这里利用了线段树的天然二分性,如果左子树的值>=k也就是说k小值在左子树,那么就往左子树递归,否则就往右子树递归,比如查第5小,左子树只有三个,那么就往右子树递归查询第2小

ll kth(int k,int l,int r,int rt){
    //查询第k小的那个数 
    if(l==r){
        return l;
    }
    int m=l+r>>1;
    if(a[rt<<1]>=k)    return kth(k,l,m,rt<<1);
    else return kth(k-a[rt<<1],m+1,r,rt<<1|1);
}

求一个数在全局的排名

排名数值上等于全局比那个数小的数的个数+1,那么放在权值线段树里就等价于前缀和查询,因为节点维护的是区间内出现的数的次数,例如我们求x的排名我们只需要查询[1,x-1]这个区间内的前缀和,然后加1就是x的排名了。

那也就是普通线段树的区间查询了,只不过左区间恒为1罢了(surprise!)

ll query(int L,int R,int l,int r,int rt){
    //普通的区间查询 
    if(L<=l&&r<=R){
        return a[rt];
    }
    int m=l+r>>1;
    ll ans=0;
    if(L<=m)
        ans+=query(L,R,l,m,rt<<1);
    if(R>m)
        ans+=query(L,R,m+1,r,rt<<1|1);
    return ans;
}
ll frank(ll x){
    if(x==1) return 1;
    return query(1,x-1,1,n,1)+1;
}

前驱

前驱就是小于这个数(设为p)的最大的数,所以我们优先查小于x但尽量大的数,所以当右子树有小于p的时候我们优先递归右子树,直到整个区间小于p,那该区间的右端点就是我们要求的前驱了。所以我们分两部,找到整个区间刚好小于p的节点,然后查询那个节点的非空最右子树。

ll findpre(int l,int r,int rt){
    //查询节点rt的非空最右子树 
    if(l==r) return l;
    int m=l+r>>1;
    if(a[rt<<1|1]) return findpre(m+1,r,rt<<1|1);
    return findpre(l,m,rt<<1);
} 
ll pre(int p,int l,int r,int rt){
    if(r<p){
        if(a[rt]) return findpre(l,r,rt);    
        return 0;
    }
    int m=l+r>>1;
    int re; 
    if(m+1<p&&a[rt<<1|1]&&(re=pre(p,m+1,r,rt<<1|1))){                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
        return re;
    }
    return pre(p,l,m,rt<<1);
}

对于小于p的最大的数,我们设p的排名为rp,则p的前驱就是第rp-1小的数,所以我们可以通过frank和kth结合起来求前驱

ll pre(int p){
    return kth(frank(p)-1,1,n,1);
}

后继

后继就是找大于数p的最小数,思路和找前驱一样,分两步先找刚好整个区间大于p的节点,然后找节点的最左非空子树。

ll findnext(int l,int r,int rt){
    //查询节点rt的最左非空子树 
    if(l==r){
        return l;
    }
    int m=l+r>>1;
    if(a[rt<<1]) return findnext(l,m,rt<<1);
    return findnext(m+1,r,rt<<1|1);
}
ll next(int p,int l,int r,int rt){
    if(p<l){
        if(a[rt]) return findnext(l,r,rt);
        return 0;
    }
    int m=l+r>>1;
    int re;
    if(m>p&&a[rt<<1]&&(re=next(p,l,m,rt<<1))){
        return re;
    }
    return next(p,m+1,r,rt<<1|1);
}

对于p的后继也一样,设p+1的排名为rp,那么p的后继就是第rp小的数

ll next(int p){
    return kth(frank(p+1),1,n,1);
}

复杂度分析

模板

#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=
int a[maxn];
long long ans;
void pushup(int rt){
    a[rt]=a[rt<<1]+a[rt<<1|1];
}
void update(int pos,int val,int l,int r,int rt){
    if(l==r){
        a[rt]+=val;
        return;
    }
    int m=l+r>>1;
    if(pos<=m)
        update(pos,val,l,m,rt<<1);
    else
        update(pos,val,m+1,r,rt<<1|1);
    pushup(rt);
}
ll kth(int k,int l,int r,int rt){
    //查询第k小的那个数 
    if(l==r){
        return l;
    }
    int m=l+r>>1;
    if(a[rt<<1]>=k)    return kth(k,l,m,rt<<1);
    else return kth(k-a[rt<<1],m+1,r,rt<<1|1);
}
ll query(int L,int R,int l,int r,int rt){
    //普通的区间查询 
    if(L<=l&&r<=R){
        return a[rt];
    }
    int m=l+r>>1;
    ll ans=0;
    if(L<=m)
        ans+=query(L,R,l,m,rt<<1);
    if(R>m)
        ans+=query(L,R,m+1,r,rt<<1|1);
    return ans;
}
ll frank(ll x){
    if(x==1) return 1;
    ll ans;
    return query(1,x-1,1,n,1)+1;
}
ll findpre(int l,int r,int rt){
    //查询节点rt的非空最右子树 
    if(l==r) return l;
    int m=l+r>>1;
    if(a[rt<<1|1]) return findpre(m+1,r,rt<<1|1);
    return findpre(l,m,rt<<1);
} 
ll pre(int p,int l,int r,int rt){
    if(r<p){
        if(a[rt]) return findpre(l,r,rt);    
        return 0;
    }
    int m=l+r>>1;
    int re; 
    if(m+1<p&&a[rt<<1|1]&&(re=pre(p,m+1,r,rt<<1|1))){                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
        return re;
    }
    return pre(p,l,m,rt<<1);
}
ll findnext(int l,int r,int rt){
    //查询节点rt的最左非空子树 
    if(l==r){
        return l;
    }
    int m=l+r>>1;
    if(a[rt<<1]) return findnext(l,m,rt<<1);
    return findnext(m+1,r,rt<<1|1);
}
ll next(int p,int l,int r,int rt){
    if(p<l){
        if(a[rt]) return findnext(l,r,rt);
        return 0;
    }
    int m=l+r>>1;
    int re;
    if(m>p&&a[rt<<1]&&(re=next(p,l,m,rt<<1))){
        return re;
    }
    return next(p,m+1,r,rt<<1|1);
}
View Code

猜你喜欢

转载自www.cnblogs.com/qq2210446939/p/12584797.html
今日推荐