李超线段树笔记
线段树之标记永久化
普通的线段树在做区间修改时依赖懒标记(lazy tag),当我们从一个点向下访问时,需要将标记 pushdown。能否避免如此多的 pushdown 操作呢?这时需要用到标记永久化技巧。
我们要做的就是将 lazy tag 永久地留在当前的结点,这时子树中的所有结点都不会被这个 tag 所影响。因此,子树中询问的最大值 = 实际最大值 - tag。当我们想得到正确答案时,只要将子树返回的最大值加上当前 tag 即可。
标记永久化存在局限性,需要满足不同的修改操作可以交换顺序,或者说对答案的贡献是独立的这一条件。
举个例子:区间设置+区间加法,先设置后加和先加后设置的结果是不一样的,因此不能交换顺序。如果使用标记永久化,就可能改变了这个顺序。
- 比如我们先设置后加,并且令设置的区间比加的区间大,因此加的 tag 在下方,设置的 tag 在上方。
- 根据前面的方法,我们会从下往上取 tag,也就是先加法,再设置。
- 这时我们发现,由于上层是一个设置操作,下面的所有答案最终都变成了设置的那个数字,下层操作就失效了。显然有问题。
总结:标记永久化就是不再下放标记,而是让标记永久地停留在当前结点上。在统计答案时再考虑标记的影响。
复杂度分析:由于标记不会下放,但如果有两个标记落在了一个结点上,我们不会分别存储这两个标记,而是加起来合成一个标记($(+2) + (+3) = (+5)$)。因此,每个结点最多只有一个标记。询问时最多考虑 $\log n$ 个标记,复杂度和普通线段树相同 $O(\log n)$。
程序实现:
|
|
李超线段树
例题:Luogu-4097 [HEOI2013]Segment 要求在平面直角坐标系下维护两个操作(强制在线):
- 在平面上加入一条线段。记第 $i$ 条被插入的线段的标号为 $i$,该线段的两个端点分别为 $(x_0,y_0)$,$(x_1,y_1)$。
- 给定一个数 $k$,询问与直线 $x = k$ 相交的线段中,交点纵坐标最大的线段的编号(若有多条线段与查询直线的交点纵坐标都是最大的,则输出编号最小的线段)。特别地,若不存在线段与给定直线相交,输出 $0$。
操作总数 $1 \leq n \leq 10^5$,$1 \leq k, x_0, x_1 \leq 39989$,$1 \leq y_0, y_1 \leq 10^9$。
将问题转化成以下的操作:
- 加入一个一次函数,定义域为 $[l,r]$;
- 给定 $k$,求定义域包含 $k$ 的所有一次函数中,在 $x=k$ 处取值最大的那个,如果有多个函数取值相同,选编号最小的。
注意:当线段垂直于 $y$ 轴时,如果按照一般的式子计算,会出现除以零的情况。假设线段两端点分别为 $(x,y_0)$ 和 $(x,y_1)$,$y_0<y_1$,则插入定义域为 $[x,x]$ 的一次函数 $f(x)=0\cdot x+y_1$。
首先,我们建立一棵线段树,每个结点代表一段 $x$ 轴上的区间,结点的懒标记表示一条线段,记为 $g$。
现在我们要插入线段 $f$。一直找到被 $f$ 完全覆盖的区间,进行分类讨论。
如图,按新线段 $f$ 取值是否大于原标记 $g$,我们可以把当前区间分为两个子区间。其中 肯定有一个子区间被左区间或右区间完全包含,也就是说,在两条线段中,肯定有一条线段,只可能成为左区间的答案,或者只可能成为右区间的答案。我们用这条线段递归更新对应子树,用另一条线段作为懒标记更新整个区间,这就保证了递归下传的复杂度。当一条线段只可能成为左或右区间的答案时,才会被下传,所以不用担心漏掉某些线段。
具体来说,设当前区间的中点为 $mid$,我们拿新线段 $f$ 与原最优线段 $g$ 与 $x=mid$ 的交点的值作比较。
- 如果新线段 $f$ 更优,则将 $f$ 和 $g$ 交换。转化成了第 2 种情况。
- 对于中点处 $f$ 不如 $g$ 优的情况:
- 若在左端点处 $f$ 更优,那么 $f$ 和 $g$ 必然在左半区间中产生了交点,$f$ 只有在左区间才可能优于 $g$,递归到左儿子中进行下传;
- 若在右端点处 $f$ 更优,那么 $f$ 和 $g$ 必然在右半区间中产生了交点,$f$ 只有在右区间才可能优于 $g$,递归到右儿子中进行下传;
- 若在左、右端点处 $g$ 都更优,那么 $f$ 不可能成为答案,不需要继续下传。
除了这两种情况之外,还有一种情况是 $f$ 和 $g$ 刚好交于中点,在程序实现时可以归入 $f$ 不如 $g$ 优的情况,结果会往 $f$ 更优的一个端点进行递归下传。
最后将 $g$ 作为当前区间的懒标记。
查询时,用到标记永久化。我们只需要找到所有覆盖了 $k$ 的区间,每次考虑这个标记对答案有怎样的贡献即可。
复杂度分析:查询的时间复杂度显然为 $O(\log n)$,而插入过程中,我们需要将原线段拆分到 $O(\log n)$ 个区间中,对于每个区间,我们又需要花费 $O(\log n)$ 的时间递归下传,从而插入过程的时间复杂度为 $O(\log^2 n)$。
|
|
习题
- Luogu-P4254 [JSOI2008]Blue Mary 开公司 本题给出的是直线,比例题更简单