下标线段树
简介
“简介”章节摘自 OI-Wiki。
线段树是算法竞赛中常用的用来维护 区间信息 的数据结构。
线段树可以在 $O(\log N)$ 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。
线段树维护的信息在很多时候可以认为是满足(幺)半群的性质的信息。
一个幺半群 $M=(S,\circ,e)$,其中 $\circ$ 为在集合 $S$ 上定义的二元运算符,幺半群具有以下性质:
- 封闭性:$\forall x \in S$ 和 $\forall y \in S$ 有 $x\circ y \in S$。
- 结合律:$\forall x,y,z \in S$ 有 $(x \circ y) \circ z=x \circ (y \circ z)$。
- 存在幺元:即 $\exists e \in S$ 满足 $\forall x \in S$ 有 $e \circ x=x$,$e$ 为左幺元;或 $x \circ e=x$,$e$ 为右幺元。
我们观察到线段树上的信息一般满足这样的性质,一些数域上的加法与乘法自然,考虑二元的 $\max(x,y)$ 运算,此时幺元为 $-\infty$ 也满足这样的性质(一般左右幺元相同时简称为幺元)。
线段树的构造
线段树的基本结构与建树
线段树将每个长度大于 $1$ 的区间划分成左右两个区间递归求解,把整个线段划分为一个树形结构,通过合并左右两区间信息来求得该区间的信息。这种数据结构可以方便的进行大部分的区间操作。
举例:$\mathrm{val}=\{11,12,13,14,15,16\}$
此处应有图片
void build(int u,int l,int r){
//对原数组 a 的 l-r 范围建立线段树,当前节点为 u
if(l==r){//长度为 1
val[u]=a[l];
return;
}
int m=(l+r)>>1;//一种更好的写法是 int m=l+((r-l)>>1);
build(u<<1,l,m);//递归建立左子树
build(u<<1|1,m+1,r);//递归建立右子树
val[u]=val[u<<1]+val[u<<1|1];//pushup 操作
}
如果用堆式建树,对于一个长度为 $n$ 的数列,线段树的数组大小需要开到 $4n$。
有一种方法只需要大小为 $2n$ 的数组:
C++inline void Index(int l,int r){return l+r|l!=r;}
线段树的区间查询
一般地,如果要查询的区间是 $[l,r]$,则可以将其拆成最多为 $O(\log n)$ 个极大的区间,合并这些区间即可求出 $[l,r]$ 的答案。
此处以查询 $[l,r]$ 的区间和为例。
C++int query(int u,int l,int r,int ql,int qr){ //[l,r] 为查询区间,[ql,qr] 为当前节点包含的区间,u 为当前节点的编号 if(ql<=l&&r<=qr)return val[u]; int m=(l+r)>>1,ans=0; if(ql<=m)ans+=query(u<<1,l,m,ql,qr); if(qr>m)ans+=query(u<<1|1,m+1,r,ql,qr); return ans; }
线段树的区间修改与懒惰标记
一般地,如果要修改的区间是 $[l,r]$,则可以将其拆成最多为 $O(\log n)$ 个极大的区间,在这些区间打懒标记即可快速修改 $[l,r]$ 的值。当递归到懒标记所在节点的子树时,就将懒标记下传。
线段树的扩展版本
- 下标线段树的卡常版本——zkw 线段树,一种非递归的写法,但这种写法的使用范围稍窄。
- 如果下标范围过大,但操作数量不大,可以使用 动态开点线段树。
- 线段树也可以维护值域,即每个值的出现次数,这个叫做 值域线段树。
- 线段树也可以支持可持久化,这个叫做 可持久化线段树。
- 两个值域相同的线段树可以合并,这个操作叫做 线段树合并。
例题:线段树维护区间数字计算(区间加减乘除,区间求和,区间最值等)
| 名称 | 编号 | 备注 | 题解 |
|---|---|---|---|
| I Hate It | Luogu P1531 | 单点修改、区间最值 | 提交记录 备份 |
| 【模板】线段树 1 | Luogu P3372 | 区间加、区间求和 | 提交记录 备份 |
| 【模板】线段树 2 | Luogu P3373 | 区间乘、区间加、区间求和 维护三个标签:区间和 sum、区间乘法懒标记 mul 和区间加法懒标记 add。 |
提交记录 备份 |
| 区间加、区间求平均数、区间求方差 | |||
| 区间加区间sin和 | Luogu P6327 | 区间加、区间 $\sin$ 值和 | 提交记录 备份 |
| 小白逛公园 | Luogu P4513 | 单点修改和区间内最大子区间和的查询 | 提交记录 备份 |
| 线段树维护区间加法、区间除法(向下取整)、区间求最小值、区间求和 | |||
| 线段树维护区间加法、区间开平方根(向下取整)、区间求和 | |||
| 相关分析 | SDOI2017R1D2T3 | tag 非常多 | |
| 值域倍增分块,下标线段树 + 底层暴力以优化空间 |
例题:线段树维护区间数字计算(循环型的操作)
| 名称 | 编号 | 备注 | 题解 |
|---|---|---|---|
例题:线段树维护区间数字计算(位运算)
| 名称 | 编号 | 备注 | 题解 |
|---|---|---|---|
| Interesting Array | CF482B/CF483D | 线段树维护位运算;也可以用前缀和、差分做(见 此处) | 提交记录 备份 |
| 对 $\land$(按位与)运算和 $\lor$(按位或)等运算进行势能分析 |
例题:线段树维护区间括号序列、0/1 串
| 名称 | 编号 | 备注 | 题解 |
|---|---|---|---|
| 线段树维护区间括号序列 | |||
例题:线段树维护区间集合
| 名称 | 编号 | 备注 | 题解 |
|---|---|---|---|
| 苦涩 | Luogu P7476 | 线段树上维护集合,$O((n+m) \log^2 n)$ | 提交记录 备份 |
例题:线段树自身的性质
| 名称 | 编号 | 备注 | 题解 |
|---|---|---|---|
| 这道题要从线段树上的某个节点 $[l,r]$ 对 $[x,y]$ 造成的影响的角度来看! | |||
例题:线段树求解满足条件的子区间数量
| 名称 | 编号 | 备注 | 题解 |
|---|---|---|---|
| 下标线段树,构造 | |||
| 线段树 + 双指针 |
例题:线段树实现区间数字判重
| 名称 | 编号 | 备注 | 题解 |
|---|---|---|---|
| 区间数字判重 | |||
| 判断一个区间内的所有数排序后是不是公差为 $k$ 的等差数列 | |||
例题:线段树维护函数
| 名称 | 编号 | 备注 | 题解 |
|---|---|---|---|
例题:线段树上二分
| 名称 | 编号 | 备注 | 题解 |
|---|---|---|---|
| 线段树上二分 | |||
| Bash and a Tough Math Puzzle | CF914D | 最大公约数,线段树上二分 | 提交记录 备份 |
| Tunnel Warfare | POJ 2892 | 查询包含某点的最长连续区间,标记区间内是否全为 $0$,线段树上二分 | 提交记录 备份 |
| 分类讨论,线段树,二分 |
例题:兔队线段树
| 名称 | 编号 | 备注 | 题解 |
|---|---|---|---|
| 线段树(需要考虑左右子树之间的影响) | |||
| 下标线段树,倍长数组,答案为 $n-1+\min\limits_{i=1}^n\{i+\max\limits_{j=i}^{2n}\{t_j-j\}\}$ 用线段树维护 $t_j-j$ 组成的单调栈(从右到左加入,保证单调递增) |
例题:线段树维护分治结构
| 名称 | 编号 | 备注 | 题解 |
|---|---|---|---|
例题:线段树分治
| 名称 | 编号 | 备注 | 题解 |
|---|---|---|---|
| 线段树分治,最长路 | |||
| 线段树分治,可撤销并查集 | |||
| 线段树分治,可撤销并查集 | |||
| 二分图 /【模板】线段树分治 | Luogu P5787 | 线段树分治,用扩展域并查集判定二分图 | 提交记录 备份 |
| 「LibreOJ Round #6」花团 | LOJ 534 | 线段树分治,0/1 背包,按照深度从上往下 DP | 提交记录 备份 |
例题:线段树优化建图
| 名称 | 编号 | 备注 | 题解 |
|---|---|---|---|
| Legacy | CF786B | 线段树优化建图,最短路 | 提交记录 备份 |
| [Pa2011] Journeys | BZOJ 3073 | 线段树优化建图,最短路 | 提交记录 备份 |
| [POI2015] PUS | Luogu P3588 | 线段树优化建图,拓扑排序 | 提交记录 备份 |
例题:动态开点线段树
| 名称 | 编号 | 备注 | 题解 |
|---|---|---|---|
| 列队 | NOIP2017(S)D2T3 | 动态开点线段树 | 提交记录 备份 |