线段树
主要有两个操作:push_up 与 push_down.
push_up
用子节点来算父节点信息,如:sum = l.sum + r.sum
push_down
父节点的修改信息下传到子节点
基本操作
push_up(u)
bulid()
将一段区间初始化为线段树modify
修改 \(\left \{ \begin{array}{} 单点&\ easy \\ 区间&\ push\_up \ hard \ 懒标记 \end{array} \right.\)query
push_down
示意:
--------------------[1,10]---------------------------
/ \
------[1,5]------- --------[6,10]-------
/ \ / \
---[1,3]--- --[4,5]-- ---[6,8]--- --[9,10]--
/ \ / \ / \ / \
--[1,2]-- -3- -4- -5- --[6,7]-- -8- -9- -10-
/ \ / \
-1- -2- -6- -7-
\(mid=[\frac{l+r}{2}]\)
\([l,mid]\ [r,mid]\)
满二叉树 \(\Longrightarrow\) 用一维数组来存整个数.
编号是x
\(\left \{
\begin{array}{}
父节点\ &\frac{x}{2} \ &x\gg 1 \\ 左儿子\ &2x\ &x\ll1 \\ 右儿子\ &2x+1\ &x\ll1|1 \end{array}
\right.\)
线段树开空间开4n
bulid
build(int u, int l, int r) {
tr[u].l = l, tr[u].r = r;
if (l == r) return;
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
push_up(u);
}
query
\([l,R]\): 当前查询的区间.
\([T_L,T_R]\): 树中节点的范围.
证明复杂度为 O(logN)
:
-
\([T_L;T_R]\subset [L,R]\) 直接返回.
-
\([L,R]\land[T_L,T_R]\ne\varnothing\)
-
TL|----mid----|TR L|------------|R TL <= L <= TR <= R
\(\left \{ \begin{array}{} l>mid \ 只递归右边 \\ l \leq mid \left \{ \begin{array}{} 递归左边\\ 递归右边 \end{array} \right.\end{array} \right.\)
看似是递归两边,其实右边再递归到下一层就返回了.
-
TL|----mid----|TR L|-----------|R
与上面对称
-
TL|--------mid--------|TR L|---------|R
\(\left \{ \begin{array}{} R \le mid \ 递归左\\ L>mid\ 递归右\\ 其他\ 递归两边\end{array} \right.\)
-
递归两边只会发生一次,因为一旦分裂,就会变成情况1.
-
-
\([L,R]\land[T_L,T_R]=\varnothing\) 不存在