OI+ACM 笔记:B - 字符串

 


B - 字符串

哈希

哈希

  • 在 hash 值不同时,两个数据一定不一样;在 hash 值相同时,两个数据有极大概率一样。
  • 在设计有多关键字的 hash 函数时,应确保每一个关键字都得以体现。

查表映射:将原数值 x 通过随机数得到一个新的随机数值 cx 并存入表中,以打乱原来可能有的某些性质。

位运算映射:将原数值 x 通过位运算得到一个新的数值 shift(x),以打乱原来可能有的某些性质。

字符串哈希:字符串哈希要求有序。将字符串 s 视为一个 base 进制数,即 f(s)=sibaseli(modM)。模数 M 尽量大,尽量为质数。

f(s[1:i])=f(s[1:i1])×base+si

字符串哈希的子串哈希值

f(s[l:r])=f(s[1:r])f(s[1:l1])baserl+1

集合哈希:集合哈希要求无序。将集合的元素通过某种法则(如位运算映射)转化后,通过某种具有交换律的运算(如加法、异或)结合。

  • 使用加法结合(和哈希):

f(S)=xSshift(x)

  • 使用异或结合(异或哈希):此时可以将出现次数为偶数的元素的影响排除,将出现次数为奇数的元素的影响更改为仅一次。

f(S)=xSshift(x)

异或哈希判完全平方数:定义完全异或性哈希函数 f(x),合数 xf(x) 定义为 x 所有质因子的 f 函数的异或和(多个相同质因子需要重复异或)

  • 先给 1n 中的所有质数 pf(p) 赋一个范围较大的随机数,其他 1n 中的所有合数 xf(x) 通过线性筛得出,特别地 f(1)=0
  • x 为完全平方数,当且仅当 f(x)=0

KMP

参考 1:https://www.luogu.com.cn/blog/command-block/border-li-lun-xiao-ji。
参考 2:https://www.luogu.com.cn/blog/ix-35/noi-yi-lun-fu-xi-ii-zi-fu-chuan。

字符串的周期:对字符串 s0<p|s|,若 si=si+p 对所有 i[1,|s|p] 都成立,则称 ps 的周期。特别地,若 |s| 整除 p,则称 ps 的整周期。

字符串的 border:对字符串 s0r<|s|,若 s 长度为 r 的前缀和后缀相等,就称 s 长度为 r 的前缀为 s 的 border。

前缀函数 nexti:字符串 s[1:i] 的最长 border 的长度。

  • 性质:s 有长度为 r 的 border |s|rs 的周期。
  • 性质:s 的最长 border 接上该最长 border 的所有 border 恰好为 s 的所有 border。
    • s[1:i] 的所有 border 为 nexti,nextnexti,
  • 性质(弱周期引理):若 p,qs 的周期,且 p+q|s|,则 gcd(p,q) 也是 s 的周期。
  • 性质(周期引理):若 p,qs 的周期,且 p+qgcd(p,q)|s|,则 gcd(p,q) 也是 s 的周期。
  • 性质:若 ST 的前缀,S 有整周期 aT 有周期 babb|S|,则 T 也有周期 a
  • 性质:若 ST 中的某两个匹配 S1,S2 有重合部分,且起始位置相差 d,则 SS1S2 均有周期 d
  • 性质:若 |S||T|2,则 ST 中的匹配起始位置形成等差数列。
  • 性质(短周期结构):s 的所有不大于 |s|2 的周期,都是其最短周期的倍数。
  • 性质(长 border 结构):s 的所有不小于 |s|2 的 border,长度构成一个等差数列。
  • 性质:s 的所有 border 按长度排序后,可以被划分成 O(log|s|) 个等差数列。
  • 性质:s 的所有公差 d 的 border 的等差数列总大小是 O(nd) 的。

Z 算法

Z 函数:字符串 ss[i:n] 的最长公共前缀(LCP)长度,记作 Zi。特别地,Z1=0

流程:考虑从前往后扫。维护一个右端点最靠右的、和前缀匹配的子串。不妨记作 b[l,r]

  • r<i,则

Zi0

  • ri,则

Zimin(Zil+1,ri+1)

时间复杂度:注意到单次枚举的次数为 max{l+Zl1} 的增量,故时间复杂度为 O(n)

Manacher

转化:回文串按照长度分成 奇 / 偶 两类,奇回文串的对称轴处于一个字符上,偶回文串的对称轴处于两个相邻字符的中间。可以在一个字符串的首尾以及所有的相邻两字符之间插入一个原串中未出现的字符,如 #, $, ^。这样原串中每一个回文串,都可以对应到新串中每一个极长的奇回文串。

数组 pi:以 si 为中心的最长回文子串的半径(含中心)。

流程:考虑从前往后扫。维护一个右端点最靠右的回文子串,不妨记该回文串的中心为 k

  • k+pk1<i,则

pi1

  • k+pk1i,则

pimin(k+pki,p2ki)

时间复杂度:注意到单次枚举的次数为 max{k+pk1} 的增量,故时间复杂度为 O(n)

Trie

trie:trie 是一个纯正的自动机,形态是树。一个起点,若干终点。多模式串的所有前缀,与 trie 上的所有状态一一对应。

trie 中的边:trie 主要考虑一种转移边,表示在某个状态所表示的串后面加一个字符。

  • 0/1 trie 支持持久化,可以作为 0/1 trie 的前缀和来使用。
  • 0/1 trie 合并单次复杂度,取决于两棵 0/1 trie 的交集大小;0/1 trie 合并总复杂度,取决于所有 0/1 trie 的节点总数。
  • 0/1 trie 合并支持可持久化,需要在合并时新建节点。

0/1 trie 中查询,与 x 异或的最大值:从高位 低位贪心,有异走异,无异走同。

0/1 trie 中查询,与 x 异或的第 k 大值:从高位 低位考虑

  • 设当前考虑到第 i 位,设 x 的异位里有 cnt 个数:
    • kcnt,则寻找 x 的异位中与 x 异或的第 k 大值。
    • k>cnt,则寻找 x 的同位中与 x 异或的第 kcnt 大值。

0/1 trie 中查询,与 x 异或 k 的信息:从高位 低位考虑

  • 设当前考虑到第 i 位:
    • k 的第 i 位为 1,走 x 的异位。
    • k 的第 i 位为 0,计算 x 的异位的贡献,走 x 的同位。
  • 考虑完所有位之后,计算当前叶子节点的贡献。

0/1 trie 中修改,令全局异或 x:从高位 低位考虑,运用懒标记,设当前考虑到第 i 位,若懒标记的第 i 位为 1,则交换左右儿子。

0/1 trie 中维护集合异或和,支持插入、删除、全局 +1:从低位 高位考虑

  • 需要维护:
    • trans:转移边。
    • cnt:以当前状态为根的子树内的结点数。
    • xr:以当前状态为根的子树内的异或和。
  • 插入、删除:递归操作即可。
  • 全局 +1:交换当前节点的 0/1 儿子,进入 0 转移边(没交换前是 1 转移边)的儿子递归。
    • 考虑二进制下 +1,相当于从低位 高位找到第一个 0,将其改为 1,将前面的所有 1 改为 0

Trick:CF241B。

AC 自动机

AC 自动机:AC 自动机是一个状态机,形态是 trie。一个起点,若干终点。多模式串的所有前缀,与 AC 自动机上的所有状态一一对应。

AC 自动机中的边:AC 自动机主要考虑两种边:

  • 普通转移边:表示在某个状态所表示的串后面加一个字符。
  • 失配指针 fail:表示某个状态所表示的串的最长后缀。这类边构成一棵树,称作 fail 树。不一定满足 faili<i

trie 图:对于不存在的转移 δ(p,c),指向 δ(failp,c)。本质上是路径压缩。

trie 图中的普通转移边:从状态 s 指向状态 s+c,其中 ss 的满足 s+c 存在的最长后缀。

  • 建 trie 图时,当字符集很大时,需要用到 std::map可持久化数组建 trie 图。

SA

后缀数组 sai:排名为 i 的后缀。

排名数组 rki:后缀 i 的排名。

LCP 数组 heighti:后缀 sai1,sai 的最长公共前缀,即 heighti=LCP(sai1,sai)

性质:后缀 sai,saj 的最长公共前缀为

mini<kj{heightk}

性质(序列 SA)

heightrkiheightrki11

性质(序列 SA)证明

  • heightrki1=0 时,命题成立。
  • heightrki11 时,考虑到后缀排序将前缀相近的后缀放到一起,所以 heighti 可以看做字典序小于 sai 的后缀与 sai 的 LCP 最大值,记 u=sarki11,必有 su=si1,则 s[u+1:n] 的字典序比 s[i:n] 的字典序小,则必然存在一个后缀使得与其 LCP heightrki11,故命题成立。

序列 SA 求法

  • O(nlog2n) 求序列 SA:sort + 倍增 + hash。当比较函数时间开销较大时,使用 std::stable_sort()
  • O(nlogn) 求序列 SA:倍增 + 双关键字排序。
  • O(n) 求序列 SA:DC3、SA-IS。

树上 SA 求法

  • O(nlog2n) 求树上 SA:sort + 树上倍增 + hash。当比较函数时间开销较大时,使用 std::stable_sort()
  • O(nlogn) 求树上 SA:倍增 + 双关键字排序。

本质不同子串个数

n(n+1)2i=2nheighti

SAM

SAM 与其他后缀数据结构的关系

  • 后缀 trie:一个字符串 s 的所有后缀组成的 trie。
  • 后缀 trie 上建 AC 自动机的 fail 指针:表示删去某个状态所表示的串的首字符。
  • 后缀树:后缀 trie 虚树化(仅保留所有后缀在后缀 trie 上的终止节点,及两两 LCA)。
  • 后缀自动机的 parent 树:反串的后缀树。

SAM:SAM 是一个状态自动机,形态是 DAG。一个起点,若干终点。原串的所有子串,与 SAM 上从起点开始、任意点结束的所有路径一一对应、不重不漏。

SAM 中的边:SAM 主要考虑两种边

  • 普通转移边:表示在某个状态所表示的串后面加一个字符。
  • 后缀链接 link:表示将某个状态所表示的最短串的首字母删除。这类边构成一棵树,称作 parent 树。不一定满足 linki<i

SAM 节点个数2n1

SAM 转移个数3n4

  • 建 SAM 时,当字符集很大时,需要用到 std::map 建 SAM。

endpos(s):子串 s 的所有终止位置集合(right 集合)。

  • SAM 中的每一个状态都对应一个 endpos 等价类。
  • SAM 中的每一个状态(endpos 等价类),在其所包含的所有子串中,短串为长串的后缀,长度构成一个连续段。
  • s1s2 的后缀,有 endpos(s1)endpos(s2);否则,有 endpos(s1)endpos(s2)=

endpos(s) 的维护:在 parent 树上运用可持久化线段树合并维护。

广义 SAM待填。

在某个状态所表示的串前面删一个字符:若当前串的长度等于当前状态的 minl,跳后缀链接即可。

在某个状态所表示的串前面加一个字符:维护每个状态 uendpos 集合中任意一个元素 posu,即可在原串中定位状态 u

  • 若当前串的长度等于当前状态的 maxl,则相当于要在 parent 树上向一个儿子节点走。在建 parent 树的时候,预处理每一个状态前加一个字符,能走到哪个儿子即可(枚举 u 与其每个儿子 v,在原串中定位即得从 uv 需加什么字符)。
  • 若当前串的长度小于当前状态的 maxl,则相当于要考虑当前状态是否可以容纳新串,只需判断新加的字符是否与原串对应位置上的字符匹配即可。

子串定位(求 s[l:r] 在 SAM 中的对应状态):先定位 s[1:r] 在 SAM 中的对应状态(在构造 SAM 的过程中记录即可),在 parent 树上倍增,找到 maxl 大于等于 rl+1 且深度最浅的状态。

子串匹配(给定模式串 S 与文本串 T,对每个 r 求最小的 l 使得 T[l:r]S 的子串):采用增量法。维护当前匹配到的子串长度在 SAM 上的对应状态,当右端点扩展时,不断跳后缀链接,直到跳到起点或存在相应的转移边为止,若存在相应的转移边,则走该相应的转移边。

子 SAM(给定模式串 S 与文本串 T,令 TS[l:r] 中匹配):判断由 S[l:r] 组成的 SAM 是否存在相应的转移边,首先在原 SAM 中就需要存在相应的转移边,其次在原 SAM 中新状态的 endpos 集合需要在 [l+len1,r] 中有元素。失配时,应尝试 lenlen1 后继续查询,而非直接跳失配指针。

PAM

PAM:PAM 是一个状态自动机,形态是由两棵树构成的森林。有两个起点(偶根 0,奇根 1),若干终点。原串的所有回文子串,与 PAM 上的所有状态对应。

PAM 中的边:PAM 主要考虑两种边:

  • 普通转移边:表示在某个状态所表示的串前后各加同一个字符。
  • 后缀链接 link:表示某个状态所表示的串的最长回文后缀。这类边构成一棵树,称作 parent 树。

PAM 节点个数(本质不同回文子串个数)n

  • 在任意一个串后面加一个字符,运用反证法可知新增的本质不同回文子串个数至多 +1

PAM 的 half 指针:表示某个状态所表示的串,长度小于等于该串一半的最长回文后缀。

posted @   Calculatelove  阅读(146)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示