权值线段树
权值线段树
定义:
普通线段树维护的是数列的区间信息,而权值线段树维护了一列数中数的个数。
也就是说,我们的权值线段树就是用线段树维护了一堆桶
例子:
给定一个排列:
\(1,1,1,2,3,4,5\)
其中,一棵线段树的叶子节点维护的是:有几个 \(1\),有几个 \(2\)。
他们的父亲节点维护的是有几个 \(1\) 和 \(2\)。
区别:
权值线段树按照值域开空间,维护的是个数,而普通线段树则根据数的个数开空间,维护数本身信息。
用途:
权值线段树可以解决 数列第 \(k\) 大/小的问题
整棵线段树的根节点表示整个值域有几个数。
模板:
void pushup(int x){
t[x]=t[x<<1]+t[x<<1|1];
}
void build(int x,int l,int r){
if(l==r){
t[x]=a[l]; return;
}
int mid=l+r>>1;
build(x<<1,l,mid); build(x<<1|1,mid+1,r);
pushup(x);
}
void update(int x,int l,int r,int c,int cnt){//表示数k的个数多cnt个
if(l==r){
t[x]+=cnt; return;
}
int mid=l+r>>1;
if(c<=mid) update(x<<1,l,mid,c,cnt);
else update(x<<1|1,mid+1,r,c,cnt);
pushup(x);
}
int query(int x,int l,int r,int c){//查询这个数有多少个
if(l==r){
return t[x];
}
int mid=l+r>>1;
if(c<=mid) return query(x<<1,l,mid,c);
else return query(x<<1|1,mid+1,r,c);
}
查询第 \(k\) 大/小值:
思路:
-
到每个节点时,如果 右子树的总和大于等于 \(k\),说明第 \(k\) 大值出现在右子树中,则递归进右子树
-
否则说明此时的第 \(k\) 大值在左子树中,则递归进左子树,注意:此时要 将 \(k\) 的值减去右子树的总和。
-
如果我们要找的是第 \(7\)大值,右子树总和为 \(4\),\(7−4=3\) ,说明在该节点的第 \(7\) 大值在左子树中是第 \(3\) 大值。
最后一直递归到只有一个数时,那个数就是答案。
int kthmax(int x,int l,int r,int c){
if(l==r){
return l;
}
int mid=l+r>>1;
if(c<=t[x<<1|1]) return kthmax(x<<1|1,mid+1,r,c);
else return kthmax(x<<1,l,mid,c-t[x<<1|1]);
}
int kthmin(int x,int l,int r,int c){
if(l==r){
return l;
}
int mid=l+r>>1;
if(c<=t[x<<1]) return kthmin(x<<1,l,mid,c);
else return kthmin(x<<1|1,mid+1,r,c-t[x<<1]);
}
引导:
权值线段树一般不会单独用,通常是和合并线段树一起用的
合并线段树的博客:My Blog
不关注的有难了😠😠😠https://b23.tv/hoXKV9