序列分治学习笔记
0x01 前言
序列分治作为一种常见的解决序列问题的算法,有着许许多多的广泛应用。下至普及,上至 NOI,都能看见它的身影。
今年 S 组第一轮完善程序 T2 就考了序列分治,虽然对于那个问题来说分治并不是最优解,但是笔者从中学到了一种序列分治的写法。这也是本文的灵感来源。
本文主要介绍较为基础的序列分治,适合初学者使用。因为笔者也是初学者。
0x02 算法流程
序列分治通常用于解决⌈子区间贡献⌋一类问题,其基本思想是,假设要计算
-
在 内。即 。 -
在 内。即 。 -
跨过 ,即 。
考虑一个递归的过程,先递归计算
边界:当
时间复杂度的计算通常使用主定理,但是笔者主定理学的很烂,所以喜欢画递归树分析。
不妨通过一道经典的例题来理解这个过程:
SP32079 ADAGF - Ada and Greenflies
- 给出一个序列
,求:
,值域大小 。
考虑对序列进行分治。设当前分治区间为
注意到固定右端点
一个跨过中点的区间一定是个左半区间的后缀拼上一个右半区间的前缀。因此考虑预处理左半区间所有后缀以及右半区间所有前缀的 __gnu_pbds::gp_hash_table
维护
最后枚举左半区间后缀的
时间复杂度可以这么计算:
设当前分治区间长度为
。 求左半区间后缀、右半区间前缀的
时, 只变化 次,这 次辗转相除的时间复杂度为 。而剩下 次 不变的时候,根据辗转相除的具体实现容易得知,这些时候求 的时间复杂度为 。
__gnu_pbds::gp_hash_table
的时间复杂度为,枚举左半区间后缀、右半区间前缀的 的时间复杂度为 。 容易发现瓶颈在于求
,当前分治区间的时间复杂度为 。 根据主定理容易得知整个算法的时间复杂度
。 用另一种方式分析,分治一共会进行
层,每一层的总时间复杂度为 ,容易发现对于任意一层 ,因此时间复杂度为 。
空间复杂度为
再来看一道例题:
双倍经验:SP10622。
三倍经验(弱化版):AGC005B。
- 给出长度为
的序列 ,求:
。
这个问题就是今年 S1 完善程序 T2。它给出了一种分治求解⌈最值贡献/限制⌋的方法。
不妨将式子拆开:
两个式子的计算是类似的,这里以最大值为例。
设当前分治区间为
考虑枚举左端点,求出以
维护一个前缀最大值数组
。
考虑从右往左枚举左端点
那么对于
边界:当
时间复杂度为
0x03 习题
Ⅰ - JOISC2014H JOIOJI
给出一个长度为
的字符串,仅由 三种字母组成,求一个最长的子串,使得其中三种字母出现次数相等。
。
这题扫描线严格优于分治。
双倍经验(弱化版):CF873B Balanced Substring。
考虑分治,设当前的区间为
设
根据题意得知区间满足的条件为:
整理得
于是在左半区间用 map
维护每一个二元组
时间复杂度为
Ⅱ - CF549F Yura and Developers
给定数组
和常数 ,求有多少个区间 ,满足:
。
。
, 。
考虑分治。设当前分治区间左端点为
首先对于右半区间,维护
从右往左扫描跨过中点的区间的左端点
说白了就是右端点取在
分别计算以
问题变成求
考虑维护
这么一来,左端点
时间复杂度为
Ⅲ - ABC282Ex Min + Sum
给出两个长为
的序列 和常数 ,求有多少个区间 ,满足:
。
考虑分治。设当前分治区间为
考虑如何统计跨过中点的区间个数。枚举左端点
对于
把右端点
-
,则符合条件的区间满足 ,那么统计有多少 满足 。 -
,则符合条件的区间满足 ,那么统计有多少 满足 。
考虑从右往左枚举左端点
使用平衡树
有人可能会说平衡树小题大做,但是 __gnu_pbds::tree
真的很方便。
时间复杂度为
Ⅳ - CF1156E Special Segments of Permutation
给定一个长度为
的排列 ,求有多少对 ,满足:
。
同样考虑分治。设当前分治区间为
对于右半区间,记录
-
对于
的区间,要统计 的 的个数,变形成 。故维护一个桶 表示 中 每种取值的出现次数,贡献为 。 -
对于
的区间,要统计 的 的个数,变形成 。故维护一个桶 表示 中 每种取值的出现次数,贡献为 。
注意到
边界:当
时间复杂度为
Ⅴ - P4755 Beautiful Pair
给定长度为
的序列 ,求有多少个区间 满足
。
。
考虑分治,设当前分治区间为
考虑
预处理数组
维护一个单调的指针
当
那么就用两棵动态开点线段树维护
边界
注意清空。可以把所有用到的节点放到一个容器里面再单独拿出来清空。
设值域为
Ⅵ - ABC248Ex Beautiful Subsequences
- 给定长度为
的排列 以及整数 ,求有多少个区间 满足 :
, 。
考虑分治,设当前分治区间为 怎么全都是这句。
考虑
预处理数组
维护两个指针
-
当
时, ;当 时, 。 -
当
时, ;当 时, 。
不难发现当
分两大种、六小种情况讨论(加粗的是结论):
-
当
时:-
当
时,满足的条件等价于 ,变形成 。即统计有多少
满足 且 。 -
当
时,满足的条件等价于 ,变形成 。即统计有多少
满足 且 。 -
当
时,满足的条件等价于 ,变形成 。即统计有多少
满足 且 。
-
-
当
时:-
当
时,满足的条件等价于 ,变形成 。即统计有多少
满足 且 。 -
当
时,满足的条件等价于 ,变形成 。即统计有多少
满足 且 。 -
当
时,满足的条件等价于 ,变形成 。即统计有多少
满足 且 。
-
我们发现六种情况本质上是四类统计,每一类统计都是二维数点。
具体地,建立四棵主席树
统计的时候,先看是哪一大类,再对于三种小类,运用结论得到要统计的权值区间,并将
边界:当
时间复杂度为
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· .NET Core 中如何实现缓存的预热?
· 三行代码完成国际化适配,妙~啊~
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?