想快速查找队列中的第k小?你需要权值线段树
想快速求得大小在一个区间中的数之和或个数?你需要权值线段树
想快速知道一个数在队列中的排名?你需要权值线段树
权值线段树,就是记录一些数里面,大小在一个区间范围内的数的信息。如下图。
因为有1个1,所以大于等于1,小于等于1的有1个数,所以(1,1)存储1,(2,2)(3,3)(4,4)(5,5)同理。
因为有1个1,1个2,1个3,所以大于等于1,小于等于3的数有3个,所以(1,3)要记录下的数为3,(4,5)(1,5)同理。
既然权值线段树可以记录这个,那么它有什么用呢?
1,快速知道查找第K小(/大):
计算前K小(/大),我们在递归的时候,可以记录下当前这个区间前(/后)有多少个数,例如(4,5)前面是(1,3),有3个数
(4,4)前面是(1,3),有三个数,(5,5)前面有(1,4),有5个数。
那么这个怎么计算呢?
如果是计算排多小,在递归入左子节点时,排名不需改变,因为当前区间的左边界便是左子节点的左边界,并不会增加数,但如果是入右子节点,则需要将排名加上左子节点区间内数的个数,因为它前面增加了那么多个数
如(5,5)前面有(1,3)+(4,4)即3+2=5个数。
2,快速求得大小在一个区间中的数之和或个数
我们可以用它统计在L~R之间的数有多少个,通过记录子树上有多少个数可以求出,
如果要统计大小在一个区间中的数之和可以用子树上的数之和求出。
3,快速知道一个数在队列中的排名
方法与1相似
而且权值线段树修改一次仅需log n(一条链)的时间,效率还是很优越的。
贴一段代码
procedure inw(x,l,r,y,tc:longint);//x:当前节点编号,l,r:左右边界,y:插入/查找的数的大小,tc:在这个区间之前有多少个数
begin
if l<>r //如果当前这个区间还可以再二分
begin
mid=(l+r)div 2; //二分
if y<=mid //判断y在左右哪个区间内
begin
if son[x,1]=0 begin //判断该点是否有左儿子
inc(total); //计算左儿子编号
son[x,1]=total; //加入左儿子
end;
inw(son[x,1],l,mid,y,tc); //沿着左儿子走,同时更新右边界
end
else
begin
if son[x,2]=0 begin //判断该点是否有右儿子
inc(total);son[x,2]=total; end; //同理
if 有左儿子 then inw(son[x,2],mid+1,r,y,tc+zs[son[x,1]]) //如果该点有左儿子,则更新排名
else inw(son[x,2],mid+1,r,y,tc);
end;
ss[x]=0;
zs[x]=0;
if 有左儿子 begin ss[x]=ss[son[x,1]],zs[x]:=zs[son[x,1]]; end;
if 有右儿子 begin ss[x]=ss[x]+ss[son[x,2]],zs[x]:=zs[x]+zs[son[x,2]]; end; //更新子树权值和,子树上的数的数量
end
else begin ss[x]=ss[x]+y; //更新子树权值和
zs[x]=zs[x]+1; //更新有多少个y
v=tc+1; //计算排名
//如果只是查询可以不更新,直接计算排名即可。
end;
end;