关于log方线段树
通常,线段树是一个log的。
但是,有的用于解决特殊问题的线段树,是两个log的。
这个额外的log有两种情况:
第一种就是添加的标记比较特殊,使得pushdown时可能递归。
通过诡异证明,使得均摊复杂度为\(O(nlog^2n)\)。
第二种就是pushup时,需要递归到其中一个子树,这样每次pushup是\(O(logn)\)的,总复杂度\(O(nlog^2n)\)。
第一种
李超线段树:用于维护凸包
支持添加一条线段,询问某个位置的y坐标最值。
以最大值为例:
考虑标记永久化维护。把一条线段拆分成若干区间。
为了保证询问的复杂度,每个区间只能维护一条线段。
考虑如何在线段树的区间上加入一条新的线段:
如果该区间没有线段或在此区间内,要加入的线段和这个区间的最优线段没有交点,则直接修改,不是最优的线段扔掉。
否则,计算出交点,保留长度较大的一条,另一条下放到交点所在的儿子区间上。
这个复杂度是\(O(logn)\)的,因此总共是\(O(nlog^2n)\)。
询问操作就是求出在所有包含它的区间的最优线段中找最优的。
具体实现参考这里
题目大多很模板。有的是放到了树上。
Segment Tree Beats:
又称吉司机线段树。
支持将区间内大于\(x\)的数全部改为\(x\)。询问区间和等相关信息。
通用思维:维护区间最大值\(zd\),最大值出现的次数\(zs\)(便于更新区间和),次大值\(cd\)。
分类讨论:
若\(x>=zd\),直接返回。
若\(cd<x<zd\),更新\(zd=x\)即可,zs不用动。同时更新区间和。
若\(x<cd\),递归左右子树即可。
注意不要忘记维护延迟标记。
对于其他操作(如区间加),正常实现即可。
时间复杂度\(O(nlog^2n)\)。
例题:[SNOI2020] 区间和
区间和\(x\)取max,询问区间最大连续子段和。
套路地,维护区间前缀后缀最大和,段内最大和,以及区间和。
同时,维护它们的最小次小值以及最小值个数。
由于每个数会逐渐变大,因此它们的长度会逐渐增加。
维护一个数\(z\),表示使得前后缀长度改变所需要的最小的\(x\)。
由于段内最大和最终来源是子树内的前后缀最大和,因此维护的\(z\)要和子树中的\(z\)取min,从而把段内最大和的变化也考虑上。
最后,再把这个\(z\)和次小值取min,按照上面的分类讨论进行即可。
最多的变长次数是线段树区间长度之和\(O(nlogn)\)。
每个变化最多在每个祖先节点都考虑一次。
因此复杂度不会超过\(O(nlog^2n)\)。
代码细节很多,考虑篇幅见此处。
第二种
这个可以维护区间内的递增序列,即前缀最大值个数。
也可以维护所有前缀最值之和。
总之就是与前后缀最值相关的内容。
建立一个函数计算某个线段树节点前面添加一个\(a\)后的前缀最大值个数。
讨论左子节点的最大值\(ma\)。
若\(ma<=a\),那么左子节点没有贡献,递归右面即可。
若\(ma>a\),那么右子节点的贡献与\(a\)无关,是一个确定值。这个值可以\(pushup\)时算出。因此只要递归左面即可。
综上所述,每个节点维护它的最大值,以及右子节点的前缀最大值个数。
\(pushup\)复杂度为\(O(logn)\),总复杂度\(O(nlog^2n)\)。