简述
权值线段树是线段树的一种,可以实现平衡树的基本操作,权值线段树的节点维护的是这个区间的数的出现次数,例如区间序列为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); }