数据分析之线段树入门

前置知识:

幺半群

对于集合 \(G\) 和二元运算 \(*\),如果满足如下条件,则称二元组 \((G, *)\) 是幺半群:

  • \(\forall g_1, g_2\in G, g_1*g_2\text{有定义并且} g_1*g_2\in G\)(封闭性)
  • \(\exists e\in G, s.t.\forall g\in G, e*g=g*e=g\)(单位元)
  • \(\forall a, b, c\in G, (a*b)*c=a*(b*c)\)(结合律)

容易发现如果一个幺半群另满足逆元存在性,则它是一个群。

引入

考虑幺半群 \((G, +)\),设数列 \(a_1=a_2=......=a_n=e\)\(e\)\(G\) 中的单位元)。另设 \((F, \circ)\) 是幺半群,其中每个元素均是定义域和值域均为 \(G\) 的函数。要求支持如下操作:

  • 给出 \(l, r\),求 \(\sum_{i=l}^ra_i\)
  • 给出 \(l, r, f\in F\),令 \(a_i\leftarrow f(a_i)\) 对一切 \(i\in[l, r]\)

线段树

为了方便起见,定义离散区间 \([l, r]=\{x|x\in \mathbb{Z}, l\leq x\leq r\}\)。为了避免和一般的区间混淆,以后所有的方括号区间都表示离散区间。

考虑一颗线段树,它的根节点是 \([1, n]\)。对于任意节点 \([l, r]\)。如果 \(l\ne r\),那么它的左儿子是 \(ls([l, r])=[l, [\frac{l+r}{2}]]\),右儿子是 \(rs([l, r])=[[\frac{l+r}{2}]+1, r]\)

为了方便表述,定义 \(s_u\)\(u\) 的子树的区间集合,定义 \(p_u\)\(u\) 的所有祖先的区间集合。

定义区间信息 \(t\) 是线段树上区间到 \(G\) 的映射,定义懒标记 \(l\) 是线段树上区间到 \(F\) 的映射。

根据定义,容易有定理:
定理:对于任意区间 \([l, r]\),存在 \(O(\log n)\) 个线段树中的区间,满足它们两两无交且并为 \([l, r]\)

定义函数 \(A(F)=(f_1\circ f_2\circ ...... \circ f_k)(e)\),其中 \(\{f_1, f_2, ......., f_k\}=F\) 并且已经按照区间长度从大到小排序。

于是有:
定理:\(a_i=A(l(s_u\cup p_u))(u\in p_{[i, i]})\)

区间信息 \(t\) 必须满足条件:任何一个操作完成后,\(t([l, r])=\sum_{i=l}^rA(l(p_{[i, i]}\setminus p_u))\)

修改操作

根据定理,设 \(|S|=O(\log n)\) 并且 \(S\) 是互不相交地构成区间 \([l, r]\) 的线段树节点集合。

\(p(S)=\bigcup_{s\in S}p(s)\)

那么修改操作只需要执行如下两步:

  • \(\forall s\in S, l(s)\leftarrow f\circ l(s), t(s)\leftarrow f(t(s))\)
  • \(\forall u\in p(S), t(u)=t(ls(u))+t(rs(u))\)(按照区间长度从小到大依次执行)。

容易归纳证明这两步能够保证区间信息 \(t\) 必须满足的条件。

查询操作

\(|S|=O(\log n)\) 并且 \(S\) 是互不相交地构成区间 \([l, r]\) 的线段树节点集合。

查询操作只需执行如下两步:

  • \(\forall u\in p(S), l(ls(u))\leftarrow l(u)\circ l(ls(u)), t(ls(u))\leftarrow l(u)(t(ls(u)))(\text{对} rs \text{同理}), l(u)\leftarrow e\)。按区间从大到小执行。(目的是令 \(l(p(S))=\{e\}\)
  • 输出 \(\sum_{s\in S}t(s)\)

为什么 \(\sum_{s\in S}t(s)=\sum_{i=l}^ra_i\)?因为左式等于 \(\sum_{s\in S}\sum_{i=l}^rA(l(p_{[i, i]}/p_u))=\sum_{s\in S}\sum_{i=l}^rA(l(p_{[i, i]}))=\sum_{s\in S}\sum_{i=l}^ra_i\)

这样,你就掌握了在 \(O(\log n)\) 的时间复杂度内完成修改和查询操作的数据结构-线段树。

查询操作(标记永久化)

使用标记永久化的技巧,只需要一步就能完成查询操作:

定义函数 \(A(F, x)=(f_1\circ f_2\circ ...... \circ f_k)(x)\),其中 \(\{f_1, f_2, ......., f_k\}=F\) 并且已经按照区间长度从大到小排序。

输出 \(\sum_{s\in S}A(l(p_s), t(s))\)

值的注意的是,这个技巧成立必须要求 \(F\)\(G\) 中元素的作用满足线性性,即 \(f(g_1+g_2)=f(g_1)+f(g_2)\)

证明比较显然。

posted @ 2025-05-03 15:54  yanzihe  阅读(94)  评论(0)    收藏  举报