浅谈权值线段树

想快速查找队列中的第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;


猜你喜欢

转载自blog.csdn.net/jzhu_ming_han/article/details/81041429