线段树学习
基础
概念
线段树(Segment tree)是基于分治的二叉树,用于在区间上进行信息统计。线段树每个节点都代表一个区间,每个叶节点代表长度为1的区间,每个内部节点\([l,r]\),左子节点\([l,mid]\),右子节点\([mid+1,r]\),其中\(mid=\lfloor\frac{l+r}{2}\rfloor\)。
除去树的最后一层,整棵线段树一定是一颗完全二叉树,树的深度为\(O(\log{N})\)。我们将根节点编号为\(1\),编号为\(x\)的节点左子节点为\(x*2\),右子节点编号为\(x*2+1\)。\(N\)个叶节点的满二叉树有\(1+2+\cdots+\frac{N}{2}+N=2N-1\)个节点,而最后一层有空余,故保存线段树数组长度不小于\(4N\)。
以区间最大值为例
- 建树
1 | struct Node { |
- 单点修改
从根节点出发,递归找到区间\([x,x]\)的节点,从下往上更新\([x,x]\)及所有父节点的信息,复杂度\(O(\log{N})\)。
1 | void update(int p, int x, int v) { |
- 区间查询
- 若\([l,r]\)完全覆盖当前节点区间,立即回溯,该节点\(dat\)为候选答案。
- 若左子节点与\([l,r]\)有重叠,递归访问左子节点。
- 若右子节点与\([l,r]\)有重叠,递归访问右子节点。
1 | int query(int p, int l, int r) { |
- 延迟标记(lazy-tag)
一般用于处理线段树的区间更新,如果某个节点被修改区间\([l,r]\)完全覆盖,以该节点为根的子树所有节点信息会发生变化,若逐一更新,一次区间修改时间复杂度为\(O(N)\),为了提高效率,每次更新只更新到更新区间完全覆盖线段树节点区间为止,在被更新节点打上标记,下次访问节点再将标记传给子节点。
什么时候用?统计量可合并、修改量可合并、通过修改量可直接修改统计量,即满⾜区间加法即可使用线段树维护信息。
本题有两种修改、一种查询取模后的结果共三种操作,向下传递lazy-tag时需考虑先后顺序,如果先加后乘则会有小数运算损失精度,所以先乘后加。
经典应用
区间最值
题意:给出\(n(1\le n\le50000)\)条信息,第\(y_i(-10^9\le y_i\le10^9)\)年降雨量为\(r_i(-10^9\le r_i\le10^9)\),给出\(m(1\le m\le10000)\)条询问,格式\(Y\)和\(X\),\(X\)年是自\(Y\)年以来降雨量最多的。这句话是真、假或有可能。
难点主要在分类讨论。
1 |
|
区间求和
题意:有3种操作(1)add x:向序列添加x,添加后仍然有序;(2)del x:删除序列中值为x的元素;(3)sum:求下标模5为3的元素和。
由于线段树不支持添加删除,采用离线做法。先离散化数组,用\(sum\)数组记录模5的5种情况和,从而添加或删除一个元素时对\(sum[1]\)修改,\(cnt\)表示线段树某节点区间数的个数,右子节点下标\(i\)在父节点对应下标\(i+lson.cnt\),那么父节点\(sum[i]\)等于左子节点\(sum[i]\)加上右子节点\(sum[(i+lson.cnt)\%5]\)。
1 |
|
题意:给定初始集合为\(\varnothing\),给出一系列(\(0\sim65535\)条)并(U)、交(I)、减(D)、被减(C)、异或(S)一个区间(区间\([0,65535]\)的子区间)命令,输出执行命令后的集合。
建立线段树,区间包括的为1,不包括的为0。将区间扩大为两倍,线段数中偶数存点,奇数存线段,即0号表示\([0,0]\),1号表示\((0,1)\),2号表示\([1,1]\),3号表示\((1,2)\cdots\)。那么
U:区间\([l,r]\)置1
I:区间\([-\infty,l)(r,\infty]\)置0
D:区间\([l,r]\)置0
C:区间\([-\infty,l)(r,\infty]\)置0,区间\([l,r]\)翻转\(0/1\)
S:区间\([l,r]\)翻转\(0/1\)
1 |
|
文章作者:wtyang
原始链接:https://antgwy.top/blog/Algorithm/Learing-Segment-Tree/
版权声明:本博客所有文章除特别声明外,均采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 许可协议。转载请注明出处!