经斩错乱的荆棘丛生,拨散诡谲的迷雾栾鸾。
李超线段树
用于求解某一个点在若干函数上的最值,支持动态修改函数的数据结构。
问题
维护如下操作:
- 加入一个一次函数 $k,b$ ,定义域为 $[l,r]$ ;
- 查询 $k$ ,求 $x=k$ 时线段的最大值,并输出该一次函数的编号。
$n\leq 10^5,1\leq k,x_0,x_1\leq 39989,1\leq y_0,y_1\leq 10^9$ 。
首先,应该考虑对于一次函数 $(x_1,y_1)\to(x_2,y_2),x_1=x_2$ 的情况,即当前线段垂直与 $x$ 轴,则选择添加一个定义域为 $[x,x]$ 的函数 $f(x)=0\cdot x+\max\{y_1,y_2\}$ 。
分析
有给定值域,支持合并性质。
考虑分块。考虑线段树。
对于询问,类似于单点查找,直接递归求解答案即可。
那么,对于每一个结点,我们维护:
$l,r$ | $val$ |
---|---|
当前结点表示的区间为 $[l,r]$ | 与直线 $x=mid$ 的交点纵坐标最大的线段 |
这是显然的。
而对于在一个区间内添加一条一次函数,即在当前区间 $[l,r]$ 内的 $f_1(x)=k_1x+b$ 上更新答案 $f_2(x)=k_2x+b$ ,需要进行一部分分类讨论。
通俗的讲,我们需要讨论当前线段与最新线段之间对当前区间,对左子区间,对右子区间分别的影响。
一共分 $5$ 种情况,除了一种是完全覆盖之外,其余就是 $(k_1,k_2),\left(f_1(mid),f_2(mid)\right)$ 的大小关系的四种排列,这些读者都可以自行进行模拟。
标记永久化
对于当前区间 $[l,r]$ 的新建线段,我们需要递归下去更新其子区间,并标记上 $tag$ ,但是,当子区间也已经存在了 $tag$ 了,而此 $tag$ 难以合并,只能够下传。
避免冲突,我们需要递归下传标记。
所谓标记永久化,是一个线段树的优化思想,即保证惰性标记不会因为某些原因溢出(例如加法标记不会爆 long long
之类的),就可以使用标记永久化,而不需要下传标记,只需要在询问的时候把标记的影响加到答案中,类似于差分求原数时输出的是原数和差分数的和。
这样操作可以降低程序常数,不同题目的标记永久化操作都不太一样。而同样,这个优化也适用于树套树和可持久化数据结构。
我们回到传递线段的问题上:
上述已经提到,我们需要对子区间进行分类讨论,如果两条线段在当前区间有交点,那么这两条线段都会在一段子区间内成为答案。可以推出【肯定有一个子区间被左子区间或右子区间完全包含】。可以理解下图思考。
图片来自 $\text{OI-Wiki}$
完全覆盖的区间递归下传,剩余部分作为惰性标记。
这样既保证了惰性标记永久化而不冲突,又使得每一条线段都不被遗漏。
实现
修改
分为下传标记和拆分线段两部分,可以写作两个函数,也可以写作一个。
1 | struct Line |
对于包含区间,就根据上述情况讨论,否则递归下传。
时间复杂度 $\mathcal{O}(\log^2n)$ 。
查询
记住要记录惰性标记的答案贡献。
1 | double query(int p,int l,int r,int k) |
时间复杂度 $\mathcal O(\log n)$ 。
补充
$\text{Lc}$ 线段树的时间复杂度是 $\mathcal O(n\log^2 n)$ 的,但常数极小,甚至能够搭配树链剖分过掉 $\text{1s-10}^5$ ,且李超线段树也能够各种灵活运用,范围也不比线段树少。
例题
Segment
从 $\text{Dyd}$ 从 $\text{l18q}$ 学来的学来的写法。
AC Code
1 |
|
Blue Mary
这题一眼就能转换为添加线段与单点极值的李超线段树板子,和上一道题基本一致。
不过,我还是第一次知道 std::floor()
返回的值是 double
,所以需要强制转换。
AC Code
1 |
|