字符串小记
12.31:写了 50%,还有很多东西还在施工中,最近 p 事太多了,呜。
字符串小记
字符串题目准则
在 OI 领域,做题尽量使用难度较低的字符串算法,使用难度较高的算法可能适得其反。
SAM
一个一直想学却鸽子的东西,有无数的人讲了无数次,但是我才第一次真正了解它。
SAM 入门,前人之述备矣。
我一开始读的 OI-wiki,前面还行,但是最后算法构造那里晕乎乎的,不建议。
https://www.cnblogs.com/zaza-zt/p/15419181.html
https://www.luogu.com.cn/article/v6v0kfpa
这两我觉得都很好,我主要读的第一篇。
广义 SAM (多串 SAM):
https://www.cnblogs.com/Xing-Ling/p/12038349.html
https://www.luogu.com/discuss/322224
广义 SAM 有多种写法,我采用 Bfs 的做法,优势是没有 corner,没有细节(就是套了个 trie)劣势是空间常熟偏大,不能在线加字符串。
几点共识:
后缀树是反串的 SAM 的 parent 树 ( link 树),所以并不用去学习后缀树算法。
在 99.99% 的题目中 SAM 的函数内部构成完全一样(广义 SAM 同理),所以不了解底层逻辑无伤大雅(比如复杂度证明之类的)
SAM 有用的性质(有很多,只列举我会的且常用的)
1.本质不同子串:\(\sum_i len_i-len_{fa_i}\)
1.广义 SAM 上每个模板串包含的节点数量和的上界以及构造
2.快速定位子串,求 区间 \([l,r]\),在 SAM 上的对应状态:在构建 SAM 时容易预处理 \(s_{1,i}\) 所表示的状态 \(pos_i\)。从 \(pos_r\) 开始在 link 树上倍增找到最浅的,len 值 ≥r−l+1 的状态 p 即为所求。
3.两个子串的 LCS,定位子串之后的 link 树上的 LCA 的 len
SAM 例题:
https://www.cnblogs.com/Meatherm/p/18221137#广义-sam-部分
写得很详细很清楚,点赞。
总结一下就是 SAM 的用法大概是:
1.利用定义做题,例如等价类所对应的是一段区间,len 以及 endpos 大小之类的直接对应题目所求的东西,那么把 SAM 拿出来处理信息就好了。
2.利用自动机的特性做匹配问题,SAM 也是自动机,和 AC 自动机一样也是做字符串匹配的高手,
模板:https://www.luogu.com.cn/problem/SP1812
这类题大概就是在 SAM 走路,然后顺带维护点信息。
3.建立反串后缀树,刻画前缀信息,许多字符串题都与 LCP 有关,而 SAM 全在刻画后缀信息,反过来建立,这类题大多时候可以平替 SA。
https://www.luogu.com.cn/problem/P4248
4.多串结构,把一个串的问题套到多个串上,增加维度,使用广义 SAM 就好了,大多数时候的分析根一个串类似。
https://www.luogu.com.cn/problem/P3346
5.维护 endpos 集合,有些题目并不是只需要 endpos 集合大小,需要具体信息,利用 线段树合并 维护出每个节点的集合,然后在上面套数据结构查询。
https://www.luogu.com.cn/problem/CF1037H
6.DS 嵌套,大概是利用 SAM 的一些树上性质转化为 DS 语言,再嵌套上 DS。
例题:https://www.luogu.com.cn/problem/P4482
把 border 转化成 SAM 语言就是找到一个最大的 \(l\le p <r\) 的 \(p\) 使得 \(p-lcs(p,r)<l\),然后 lcs 的刻画上面有些,然后转树分治,细节不展开。
注意:一定要开两倍空间 !!!
想做更多题可以去看老麦的课件,里面还是有很多好题的。
KMP
算是字符串的精髓了把。
算法流程:
维护当前前缀的最长 border,那么在字符串匹配的时候失配了直接跳 border,均摊复杂度正确。
应用题:https://www.luogu.com.cn/problem/P4696
重新定义 KMP 的相等,考虑 KMP 匹配过程,要维护数之间的相对顺序,那么每次新匹配一个数的时只需要满足一个串的偏序(大小)关系在另一个串里也适用即可,维护下标在它之前的,它的前驱下标和后继下标。若新插入的值大于前驱对应的值小于后继的值那么两个串就相等。
持久化:https://codeforces.com/problemset/problem/1721/E
维护单串 ACAM 就好了。
Border Theory
周期引理:
对于字符串 \(s\),\(p,q\) 为 \(s\) 的周期,\(p+q-\gcd(p,q)\le |s|\),则 \(\gcd(p,q)\) 也是 \(s\) 的周期。
有事去掉左部分的 \(\gcd(p,q)\) 更易于证明一些性质。
将所有 Border 按其二进制最高位进行分段,每一段内的 Border 都构成一个等差数列,也就是至多划分为 \(\log |S|\) 个等差数列。
由于等差数列是一段连续的 border,暴力跳即可找到所有等差数列。
证明:https://www.cnblogs.com/y-dove/p/14514097.html
用法:
1.找到 log 个等差数列然后辅助数据结构转移
https://www.luogu.com.cn/problem/P1393 减去算重的贡献前缀差分就好了。
练习:https://www.luogu.com.cn/problem/P4156
2.利用周期的性质加速加速跳 border
https://www.luogu.com.cn/problem/P5287
考虑暴力跳 kmp 的 border 是均摊的,导致带撤销复杂度假了。
考虑利用周期的性质,显然对于周期 \(d\),周期 \(2d\),…… \(kd\),根据周期引理,对于所有小于 \(n-d\) 的周期都是 \(d\) 的倍数,所以跳 border 的时候只用检查 \(d,2d\) (其他周期的下一个位置都和 \(2d\) 一样,然后直接跳到 \(kd\) 转移就好了)。
ACAM
即多串 KMP,在 trie 树上维护 fail 树,是做匹配的一把好手。
用法:
1.fail 树上乱跳,统计贡献,有若干模板题,不再一一列举了。
练习:SCOI2024d1t1(缺数据),ACAM 综合应用。
2.根号分治,我感觉是 ACAM 用得最多的地方,由于输入量有限,串总长度和一定,自然可以根号分治。
例题:https://www.luogu.com.cn/problem/P8571
做法大概就是憋两种本质不同的做法,合并一下就好了。
长串一般就是暴力做,配个数据结构快速查询。
短串一般就是建个虚树,在上面分析一下就好了。
这种题一般还是代码难度更大。
练习题:https://www.luogu.com.cn/problem/P8203,这种题最后难度应该也不在 ACAM 上。
吃屎题:https://www.luogu.com.cn/problem/P9997 有兴趣可以写。
匹配相关杂项
众所周知,我是懒人,不想写打字符串结构怎么办。
匹配相关有两个有趣的套路。
bitset
大概思路就是你优化一个一个匹配的过程,开个 bitset 维护所有合法 endpos,每次遇到一个新的字符就暴力 and 一下,可以配合代码理解。
https://www.luogu.com.cn/problem/CF914F
FFT
有些带通配符的匹配时候,设计一个合理的相等函数。
https://www.luogu.com.cn/problem/P4173
扩展题:https://www.luogu.com.cn/problem/CF827E,还需要一些周期相关的性质。
哈希
核心思想就是将一个很长很大的东西映射到一个数上做比较。
自然溢出是好文化,但是有些人就喜欢卡。
原理:https://www.cnblogs.com/tzcwk/p/hash-ull-hack.html
二维哈希:记得不同维度使用不同的 base,不然容易挂,测试:https://www.luogu.com.cn/problem/UVA11019
练习题:https://www.luogu.com.cn/problem/CF1968G2
回文相关
着急学习的可以看老麦博客。
绝赞更新中,笔者正在学习相关内容,欢迎催更。