从区间加一次函数到区间加多次函数最值-----区间数据结构与函数的一些东西(《浅谈函数最值的动态维护》的学习笔记)

核心问题

这个本来是打算和线段树学习笔记弄一起来着,不过今天才过的模板题

维护一组函数,支持修改函数或查询某点xxx处的最大值。

传统的做法就有李超线段树、凸包分块我独立发明过这个

DS序列

如果一个序列(n,s)(n,s)(n,s)满足:

-元素取自1−n1-n1−n,且无相邻的项相同。

-任意两个不同的数字x,yx,yx,y交替出现的次数不超过s+1s+1s+1。

那么称这个序列为DS(n,s)DS(n,s)DS(n,s)

定理:DS(n,s)DS(n,s)DS(n,s)序列的最长长度为λs(n)\lambda_s(n)λs(n),它满足λs(n)=O(n⋅α(n)O(1))\lambda_s(n)=O(n\cdot\alpha(n)^{O(1)})λs(n)=O(n⋅α(n)O(1)),这个增长非常的缓慢了,队常数s的影响几乎是线性。

:::info证明

观察得若一个序列为DS(n,s)DS(n,s)DS(n,s)序列,则该序列的任意子序列仍然是DS(n′,s)DS(n^{'},s)DS(n′,s)序列,其中n′n^{'}n′为子序列中实际出现的不同符合数。因此交替不会被操作破坏。

然后分层压缩证明

固定一个参数r,把一个符号称为:

-轻符号:它在序列中出现的次数<r<r<r

-重符号:他在序列中出现的次数≥r\geq r≥r

设原始序列长度为m=λs(n)\lambda_s(n)λs(n)

1、轻符号至多有nnn个,每个出现次数≤r\leq r≤r,所以它们总贡献最多为O(nr)O(nr)O(nr)

2、重符号每个出现至少rrr次,所以重符号的个数至多为O(λs(n)r)O(\frac{\lambda_s(n)}{r})O(rλs(n))。

删除所有轻符号后,得到一个只含重符号的序列。它是某个DS(q,s)DS(q,s)DS(q,s)序列,其中q=O(λs(n)r)q=O(\frac{\lambda_s(n)}{r})q=O(rλs(n)),因此其长度布草过λs(q)\lambda_s(q)λs(q)

于是递归:λs(n)≤O(nr)+λs(O(λs(n)r))\lambda_s(n)\leq O(nr)+\lambda_s(O(\frac{\lambda_s(n)}{r}))λs(n)≤O(nr)+λs(O(rλs(n)))

令r=α(n)r=\alpha(n)r=α(n)。由于alpha(n)alpha(n)alpha(n)增长极慢,递归每展开一层,字母表规模缩小一个α(.)\alpha(.)α(.)因子。展开常数层后,字母表规模降到常数,递归终止。

因此得到λs(n)≤O(n⋅α(n)O(1))\lambda_s(n)\leq O(n\cdot\alpha(n)^{O(1)})λs(n)≤O(n⋅α(n)O(1))

:::

DS序列与函数上包络线

上包络线

上包络线其实就是上凸包的样子。

我们有一堆函数,比如f1(x),f2(x),f3(x),...,fn(x)f_1(x),f_2(x),f_3(x),...,f_n(x)f1(x),f2(x),f3(x),...,fn(x)

对于每一个xxx,我们都找到那堆函数里值最大的哪个,把这些最高点连起来,就得到了一条曲线,就是上包络线。

显然这条曲线是分段的,不同的x的范围可能处于不同的函数。

分段数越多,说明曲线越复杂。

###这和DS序列有毛关系啊?!

我们把上包络线从左到右看,没遇到一段,就记下是哪个函数最高,得到一个数字序列。

注意到任何两个不同的函数,它们在最大值上的上的交替次数是有限的。

-如果函数图像成直线,两条直线最多交叉一次,所以最多交替2次

-成抛物线(二次函数)同理,最多3次

-更一般地,如果任意两个函数交替次数不超过s+1s+1s+1,我们就说这族函数sss阶交替地。

欸这个定义咋这么熟悉,这不就是DS数列吗。

所以他的长度(分段数)不比nnn多少

我们可以用懒标记来维护它,而不会爆炸

KTT

注意到,我们只用在切换点附近更新就行了。

基本思想

建一棵线段树,每个叶子放一个函数。

每个节点存储:

  • 当前最优函数:在这个节点管辖区间内,当前xxx下取值最大的那个函数。
  • 击败时间:从当前xxx开始,再过多久(增加多少xxx),这个最优函数会被另一个函数反超。

当xxx增大到超过某个节点的击败时间,就递归进入这个节点,更新它的最优函数,并重新计算新的击败时间。

这样,大部分时间我们什么都不用做,只有当"切换"真正发生时,才花力气去更新。

复杂度为什么对?

关键在于:每个节点的最优函数切换次数,不超过它子树内函数上包络线的分段数。而根据DS序列理论,这个分段数是O(λs(子树大小))O(\lambda_s(子树大小))O(λs(子树大小)),几乎是线性的。

总更新次数:

∑所有节点λs(子树大小)=O(λs(n)log⁡n) \sum_{所有节点}\lambda_s(子树大小)=O(\lambda_s(n)\log n) 所有节点∑λs(子树大小)=O(λs(n)logn)

每次更新需要从该节点往下走到叶子,路径长度O(log⁡n)O(\log n)O(logn),所以总复杂度O(λs(n)log⁡2n)O(\lambda_s(n)\log^2n)O(λs(n)log2n)。

查询就是普通线段树查询,O(log⁡n)O(\log n)O(logn)。

支持在线修改

如果允许中途修改某个函数的值,我们可以把每个叶子看成"时间线"上的分段函数:初始函数,然后每次修改后换成新函数。

这样子树内的函数数量变成r−l+1+∑kir-l+1+\sum k_ir−l+1+∑ki(其中kik_iki是叶子iii被修改的次数)。

这些函数是分段函数,交替阶会变成s+2s+2s+2,所以总切换次数仍然可控:

O(λs+2(n+M)log⁡n)O(\lambda_{s+2}(n+M)\log n)O(λs+2(n+M)logn)

其中MMM是总修改次数。于是KTT可以高效处理带修改的询问。

应用到最大子段和

在"区间加正数、查询最大子段和"问题中,区间整体加的值xxx就是"时间"。

每个节点的四个信息(sum、lmax、rmax、mx)都表示为关于xxx的一次函数:

-sum:斜率=区间长度,截距=原区间和

-lmax:若干个前缀和函数的上包络

-rmax:若干个后缀和函数的上包络

-mx:若干个子段和函数的上包络

我们把这些函数放进KTT。区间加时,只需将对应节点的"时间"推进,并更新各函数值和击败时间。如果击败时间变成负数,就递归更新。

势能分析可以证明总更新次数是O((n+M)log⁡2n)O((n+M)\log^2n)O((n+M)log2n)级别,从而得到对数复杂度,远优于分块。

扩展到二次函数

节点中存储的"当前最优函数"从直线变成抛物线。

"击败时间"不再是两条直线的交点,而是两个抛物线在定义域内的最近交点(可能需要解二次方程,我还不会这种东西)

若操作是给区间每个位置 iii 加上ai2+bi+ca_i^2 + b_i+cai2+bi+c(a,b≥0a,b\ge 0a,b≥0),那么每个位置的值就是一个关于iii的二次函数。

我们可以把每个位置看作一个叶子,存储其当前的二次函数系数。区间加操作相当于给这些系数整体加上一个给定的二次函数。

KTT 节点维护子树内所有二次函数的上包络线(最大值曲线),并计算击败时间。因为二次函数交替阶低,更新次数少,所以复杂度仍为 O((n+m)log⁡2n)O((n+m)\log^2 n)O((n+m)log2n) 级别。

相关推荐
LuminousCPP26 分钟前
数据结构-二叉树(五):查找、销毁与前序序列建树
c语言·数据结构·笔记·二叉树
艾莉丝努力练剑2 小时前
【AI大模型接入SDK】Provider分析与实现
c++·人工智能·学习·面试·大模型·llm
Nontee8 小时前
MySQL 插入冲突了怎么办?两种处理方式入门笔记
数据库·笔记·mysql
浔溺9 小时前
al+大数据每日学习笔记26
笔记·学习
xx~t9 小时前
嵌入式——进程与线程1
linux·c语言·学习·嵌入式·进程与线程
老当益壮梁奶奶9 小时前
Linux软件编程学习笔记(七):线程分离与线程间通信详解
linux·c语言·c++·笔记·学习
sel_910 小时前
【多轮对话论文导读(三)】多轮对话与Agent论文阅读笔记:用户模拟、轨迹生成与长期记忆
论文阅读·人工智能·笔记·深度学习·算法·机器学习
zzm62810 小时前
《SIGIR 2018论文精读|深度学习点击序列模型:对搜索页面点击行为进行序列建模》
笔记·深度学习·推荐系统·论文精读
我命由我1234510 小时前
UI 设计 7 种排列方式
xml·学习·ui·html·产品运营·产品经理·学习方法