【总结】线段树 进阶

我竟然开了这么多坑。。。ε=(´ο`*)))唉,慢慢填吧。。。

线段树 进阶

前置芝士

然后我们来讲一个好玩的东西,叫权值线段树。


权值线段树&动态开点

有一个数列,数列里的每个不同的 a i a_i ai 都有一个对应的数 v i v_i vi ,表示数列中的 a i a_i ai 的个数。

所以我们的任务就是,把 v v v 挂到线段树上。

众所周知,线段树是可以单点修改的,为了方便,我们把单点修改 x x x 规定为往数列里增加一个 x x x

我们让线段树中的 [ l , r ] [l,r] [l,r] 区间表示 a l , a l + 1 , a l + 2 , ⋯   , a r a_l,a_{l+1},a_{l+2},\cdots,a_r al,al+1,al+2,,ar 每个数出现次数的和。 [ x , x ] [x,x] [x,x] 就表示 v x v_x vx

注意,由于这里每一个数都有一个对应的结点,所以对于权值线段树来讲,区间不叫区间,叫值域。

咦咦咦?那干嘛要用线段树?用桶他不香嘛?

桶是挺香的,所以我们必须学线段树。 桶的区间修改和区间查询都是 O ( n ) O(n) O(n) 的,可以用线段树将其优化到 O ( log ⁡ 2 n ) O(\log_2n) O(log2n)

代码呼之欲出。(就是一个简单的单点修改,区间查询,建议打一打,当复习)

Code
#include<cstdio>
#define int long long //清秀的写法
const int maxn=1e5+5;//这里 a[i] 的最大范围是 1e5
struct segment_tree{
   
	int l,r;
	int sum;//sum 表示 [l,r] 中每个数出现次数的和
}a[maxn<<2];//二倍值域
void read(int&x){
   
    x=0;bool f=0;char ch=getchar();
	while(ch<'0'||ch>'9'){
   if(ch=='-')f=1;ch=getchar();}
	while(ch>='0'&&ch<='9'){
   x=(x<<1)+(x<<3)+(ch&15);ch=getchar();}
	if(f)x=-x;return;
}
void build(int p,int l,int r){
   
	a[p].l=l,a[p].r=r;
	if(l==r)return;
	int mid=l+r>>1;
	build(p<<1,l,mid);
	build((p<<1)|1,mid+1,r);
	return;
}
void Update(int p,int x){
   
	if(a[p].l==a[p].r){
   
		a[p].sum++;//找到 x 的对应结点,将其对应个数 ++ 。
		return;
	}
	int mid=a[p].l+a[p].r>>1;
	if(x<=mid)Update(p<<1,x);
	else Update((p<<1)|1,x);
	a[p].sum++;//左子树和右子树中的其中之一会 ++, 会且仅会 +1, 所以干脆直接 ++ 。
	return;
}
int Query(int p,int l,int r){
   
	if(a[p].l>=l&&a[p].r<=r)
		return a[p].sum;
	int val=0;
	int mid=a[p].l+a[p].r>>1;
	if(l<=mid)
		val+=Query(p<<1,l,r);
	if(r>mid)
		val+=Query((p<<1)|1,l,r);
	return val;
} 
signed main(){
   
	//略
	return 0;
}

以上是数据较小的情况。我们想,当 a i a_i ai 的范围很大,比如 1 e 8 1e8 1e8 的时候,我们还能这么玩吗?显然不能。于是,我们来加个名叫“动态开点”的玩意。

回顾区间修改,我们是怎么对一系列数进行修改的?

用一个懒惰标记,需要时将懒惰标记向下延伸。

那么,在不是每一个数都需要时,我们为什么要将 1 1 1 ~ m a x n maxn maxn 的每一个值域都计算一次呢?我们何不像区间修改那样,到了需要的时候再建点并计算呢?

但这样并不能实质性地优化空间,下标最大还是 n × 4 n\times 4 n×4 。于是,我们考虑换一种方式求下标。

我们定义一个 t o t tot tot,代表下标。在插入一个 a i a_i ai 时,如果 a i a_i ai 所处于的子树是空的,也就是还没有建这个子树,我们将这个子树的下标设置为 ++tot

咦?那这样还怎么确定一个结点的左子树和右子树的下标?

存呀!

我们让一个结点存放: l , r , l l , r r , s u m l,r,ll,rr,sum l<

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值