C++线段树初学-超短的理解重点

前言

之前“抄题解” (emm也不完全是),反正就是没弄懂照葫芦画瓢写了线段树。

这回就来真的写一篇博客记录下学线段树的过程(树状数组现在看起来有点简单了,就是树状数组给的信心,希望不要被线段树搞没了)

经典用法

给定包含 n 个数的数组 a ,有两种操作

  1. 给区间 [l,r] 中的数增加 x .
  2. 查询区间 [l,r] 中数的最大值.

线段树是什么

在线段树中,每个节点表示的区间由以下规则确定:

  • 根节点表示的区间为 [1,n].
  • 若一个点表示区间为 [l,r],且l ≠ r,则其有两个子节点,取m=向下取整((l+r)/2),左儿子表示 [l,m],右儿子表示 [m+1,r]

很显然,这有点分治思想,可以理解为分治思想在log₂n复杂度下的树化。又因是一个二叉树,所以树高(log n)【每层节点的区间长度是上一层的一半】。整个二叉树节点数为2n-1,这里是二叉树的一个性质:

  1. 线段树底层有 n 个点
  2. 每合并2个点需要增加1个点
  3. n个点需要合并 n-1 次,所以一共 2n-1 个点

常用的编号方法:根节点为1,x的左儿子是 2x ,右儿子编号为2x+1。

注意:采取这种编号方法需要开4n的空间

线段树怎么做

单点修改:将该点位置的值修改后,向上更新

单点查询:从上往下找,类似于单点修改,找到后返回

区间查询:寻找尽可能少的不相交区间使其并集为 [l,r]。同样也是从上往下找。

区间查询和区间修改中,绿色的点代表和我们需要查询的区间有交集,而红色的点代表我们直接将这个点的信息取走了,下面是区间查询和区间修改的部分代码。

	#define ll long long
	#define update v[k]=v[k<<1]+v[k<<1|1]	
	void pushdown(int x){
		v[x<<1]+=len[x<<1]*lazy[x];
		v[x<<1|1]+=len[x<<1|1]*lazy[x];
		lazy[x<<1]+=lazy[x];
		lazy[x<<1|1]+=lazy[x];
		lazy[x]=0;
	}
	ll query(int l,int r,int ql,int qr,int k){
		if(l==ql&&r==qr)return v[k];
		if(lazy[k])pushdown(k);
		int mid=l+r>>1;ll res=0;
		if(ql<=mid) res+=query(l,mid,ql,min(qr,mid),k<<1);
		if(qr>mid) res+=query(mid+1,r,max(ql,mid+1),qr,k<<1|1);
		return res;
	}
	void modify(int l,int r,int ql,int qr,int k,int y){
		if(l==ql&&r==qr){
			v[k]+=len[k]*y;
			lazy[k]+=y;
			return;
		}
		if(lazy[k])pushdown(k);
		int mid=l+r>>1;
		if(ql<=mid) modify(l,mid,ql,min(qr,mid),k<<1,y);
		if(qr>mid) modify(mid+1,r,max(ql,mid+1),qr,k<<1|1,y);
		update;
	}
区间查询 以我们要查询的区间[3,6]为例

在这张图里面,每行绿色/红色的点均不超过两个:

  • 在同一行绿色的点超过两个:以第2层为例,显然我们要求得的区间是与左节点(2号)的右边有交集和右节点(3号)的左边有交集,如果第3层出现3个绿色的,显然无法满足以上条件。
  • 在同一行红色的点超过两个:以第4层为例,如果5号节点变为绿色,9、10号节点是红色的,那我们讨论三种情况:
    • 1.若8号节点是红色,那么4号节点就应该是红色了,不成立
    • 2.若11号节点是红色,那么5号节点就应该是红色了,不成立
    • 3.若不是8号节点也不是11号节点,其他的节点是红色的集合中间就出现了不连续的问题,不成立

区间修改(要加上许多麻烦操作了):与区间操作的节点相同。若我们仅修改红色节点,灰色节点的值不正确。我们在红色节点处打上标记,经过时下传——懒标记。在红色节点打标记的目的就是为了保证复杂度,红色的点跟上图表示的意思是一样的,换种说法就是红色节点的集合一定是要查询的区间集合的子集。打上lazy tag后就保证了每次操作的时间复杂度是O(log n)

区间修改图示 以我们查询的区间[3,9]为例

zkw 线段树

特点:

  • 自底向上访问节点的线段树
  • 非递归,效率高,代码稍短、
  • 缺点:需要把树变成的完全二叉树。容易出错,部分功能不易实现,不能下放标记,需要标记永久化
    • 标记永久化:标记不会改变,从下往上走时遇到标记的时候就直接计算进去

树套树

  • 二维树状数组/二维线段树——例如维护一个矩形区域的和/最大值
  • 线段树 + 平衡树(比较常用)
  • 线段树 + 前缀和(主席树)
  • 线段树 + 树状数组(可修改主席树)
  • kd-tree 堆 ······

评论

  1. bbb
    1 年前
    2022-12-13 18:06:40

    hhhhhhhhhhhh

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇