习学ix53 [字符串算法] 笔记2

本文详细探讨了字符串的周期性和Border结构,包括弱周期引理、周期公理、短周期结构和长Border结构,通过实例展示了如何运用在AC自动机、哈希、SAM等算法中解决实际问题,如数字符合范围、子串查询和字符串操作计数等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我已经已关机

结论:p 是 S 的周期,当且仅当 ∣S∣−p|S|-pSp 是 S 的 Border。


弱周期引理(Weak Periodicity Lemma):若 p,qp,qp,q 是串 SSS 的周期,且 p+q≤∣S∣p+q\leq |S|p+qS,那么 gcd⁡(p,q)\gcd(p,q)gcd(p,q) 也是 SSS 的周期。

证明:不妨设 p<qp<qp<q(p=qp=qp=q 显然成立)。

对于 i>qi>qi>q,有 Si=Si−q=Si−q+pS_i=S_{i-q}=S_{i-q+p}Si=Si−q=Si−q+p;

对于 q−p<i≤qq-p< i\leq qq−p<i≤q,i+p≤∣S∣i+p\leq |S|i+p≤∣S∣,所以有 Si=Si+p=Si−q+pS_i=S_{i+p}=S_{i-q+p}Si=Si+p=Si−q+p。

因此 ∀i∈[q−p+1,∣S∣]\forall i\in [q-p+1,|S|]∀i∈[q−p+1,∣S∣] 有 Si=Si−q+pS_i=S_{i-q+p}Si=Si−q+p,这意味着 q−pq-pq−p 是 SSS 的周期。

而 (q−p)+p≤∣S∣(q-p)+p\leq |S|(q−p)+p≤∣S∣,同上可以继续减,由此模拟欧几里得算法过程,即可以得到 gcd⁡(p,q)\gcd(p,q)gcd(p,q) 是 SSS 的周期。

周期公理(Periodicity Axiom):若 p,q 是串 SSS 的周期,且 p+q−gcd⁡(p,q)≤∣S∣p+q-\gcd(p,q)\leq |S|p+qgcd(p,q)S,那么gcd⁡(p,q)\gcd(p,q)gcd(p,q) 也是 S 的周期。

短周期结构:字符串 SSS 的所有不超过 S2\dfrac{S}{2}2S 的周期都是其最短周期的倍数。

长 Border 结构:字符串 S 的所有不小于 S2\dfrac{S}{2}2S 的 Border 构成等差数列,且如果排序后延申这个数列,下一项就是 ∣S∣|S|S

字符串的周期/Border 结构:字符串 S 的所有周期或 Border 形成了 O(log⁡n)O(\log n)O(logn) 个值域不交的等差数列。


例题 1:[WC 2016] 论战捆竹竿

给定字符串 S,问 S 的所有周期的线性组合(系数为正整数)可以表示出多少个$ [1,w]$ 内的数。

O(nlog⁡n+log⁡w)O(n\log n+\log w)O(nlogn+logw)

首先分成了log⁡n\log nlogn段等差数列

同一个等差数列可以直接上

对于首项取模之后,相当于我们每个数加上kd(k≤m)kd(k\leq m)kd(km)都可以一步转移到

然后我们会发现一个等差数列里面整个环会被化成m/gcd(m,d)m/gcd(m,d)m/gcd(m,d)个环

然后我们每个环的相邻k项转移系数都是1,就可以直接单调队列维护转移做到线性

换模数原来的为m1m_1m1,现在为m2m_2m2

第一种转移是dis1idis1_{i}dis1i更新dis2dis1imod  m2dis2_{dis1_{i}\mod m_2}dis2dis1imodm2

然后我们还可以加上若干倍的km1km_1km1来更新第二个数组,注意这里的k没有限制

于是我们还是按照m2m_2m2同余分成m2gcd(m1,m2)\frac{m_2}{gcd(m_1,m_2)}gcd(m1,m2)m2个环

然后每个环我们正着更新一遍,然后倒着更新一遍就完全好了,因为没有限制所以不需要什么单调队列

定义:建立一棵 n+1n+1n+1 个结点的图,编号为 0,…,n0,\ldots,n0,,n,对于 i≥1i\ge 1i1iiiBiB_iBi 连边,这构成了一棵树,称为失配树或 Fail 树,将 BiB_iBi 称为 iii 的失配指针。


例题 2:[NOI 2014] 动物园

给定字符串 S,你需要对于所有 S[1…i]S[1\ldots i]S[1i] 求出其长度不超过 i2\dfrac{i}{2}2i 的 Border 数量。

​ 建立Fail树,然后就相当于查询一个点的二分之一dep节点的答案就好了

直接做就好了,离线非常直观qwq,当然在线也可以直接维护每个节点当前的pre/2节点是什么

多对一匹配:给定 mmm 个模式串 T1,…,TmT_1,\ldots,T_mT1,,Tm 和一个文本串 SSS,求每个 TiT_iTiSSS 中的出现次数。

注意:AC 自动机中包含了两棵树,一棵是 Trie 树,还有一棵是失配树,它们是不同的。

字符集大小可以压下来,用可持久化线段树来维护,即可做到O(log⁡∑)O(\log\sum)O(log)


例题 333:[NOI 2011] 阿狸的打字机

给定一个包含字符和操作 B,P\text{B,P}B,P 的长为 n 的操作序列,用于生成一个 Trie,顺序扫描这个序列,维护一个当前串,遇到字符就加到当前串末尾,遇到 P\text{P}P 就将当前串加入 Trie,遇到 B\text{B}B 就将当前串末尾字符删除。

接下来有 m 次询问,每次给定 x,yx,yx,y,问第 x 个加入 Trie 的串在第 y 个中作为子串出现多少次。

∣Σ∣=26|\Sigma|=26Σ=26 视为常数。

直接建模拟建trie,然后AC自动机,然后变成AC自动机fail树上数点,就做完了

复杂度一个n∗(log⁡+∑)n*(\log+\sum)n(log+)

例题 4:[POI 2000] 病毒

给定字符串集 {S1,…,Sn}\{S_1,\ldots,S_n\}{S1,,Sn},求是否对于任意正整数 m 都存在长度为 m 的串 T,使得任何 SiS_iSi 都不是它的子串。

∣Σ∣=2|\Sigma|=2Σ=2 视为常数。

我的做法是建AC自动机,然后考虑无解肯定是我们最后只能走到一个end节点上去

那就去掉所有end节点还有连接向他们的边,然后一旦剩下的图中存在一个1号点能访问到达大小≥2\geq 22的强联通分量就赢了

现在我们考虑将这个后缀字典树进行如下压缩:

  • 原本后缀字典树中每条边上带有一个字符,压缩后每条边上带有一个字符串;
  • 压缩过程:每次找出一个二度点,将它的父亲与儿子连接,边上的字符串是它的父亲到它的字符串拼上它到它儿子的字符串,然后删除这个二度点。

将这样得到的树称为后缀树(Suffix Tree,ST)

性质 1:每个 S 的子串都属于恰好一个等价类,同时属于任何一个等价类的也一定是 S 的子串。

显然

性质 2:每个 S 的后缀都是一个等价类的代表元。

这是一定的,我们保留的最基本的信息,就是后缀

性质 3:后缀树的叶结点的代表元是 S 的后缀。

是的,但是这不是双射关系,我们可能有一个后缀属于某个中间节点,比如aaaaaaa,但是不可能是多儿子的

性质 6:Startpos\text{Startpos}Startpos 集合相同的若干子串,它们的长度构成公差为 1 的等差数列,且短的串是长的串的前缀。

这个性质比较重要?可能需要区间加等差数列这样子

子串出现次数查询:求给定字符串 S 的后缀自动机中每个结点代表的字符串出现了几次。

子串定位:m 次询问,每次给定 S 的一个子串 S[l…r]S[l\ldots r]S[lr],求它在后缀自动机上属于哪个点的等价类。

好做!在r上倍增定位就好了!

扫描线+LCT 维护区间子串信息:区间本质不同子串数

扫描线线段树维护l,rl,rl,r的答案数组

然后我们发现每次加入一个新位置相当于把这个位置的一些已经出现过的后缀改成了这个位置向前的一段

那么就直接LCT维护fail树,每次若向上access使得实儿子交换就暴力打上标记即可

发现在切换实边的时候相当于区间加等差数列,线段树差分后变成区间加

时间复杂度O(nlog⁡2n)O(n\log^2 n)O(nlog2n)

广义 SAM 出现子串查询:对于 n 个串的广义后缀自动机,求出每个点对应的字符串是哪些原串的子串。

例题 12:[NOI 2015] 品酒大会

给定字符串 S,每个后缀有整数权值 aia_iai,对于每个 i∈[0,∣S∣−1]i\in [0,|S|-1]i[0,S1] 输出有多少对后缀的 LCP 长度不小于 i,并输出这些后缀对中权值之积的最大值。

例题 13−2:

(题目来源:我的口胡)String Master XL

维护一个初始为空的字符串 S,支持 n 次操作,操作为:

  • 末尾插入;
  • 末尾删除;
  • 给定字符串 T 查询其在当前 S 中的出现次数。

如果没有2操作,一个做法好像就是AC自动机,离线建出所有模式串ACAM,然后S上去跑,记录所有ACAM和S的某个后缀的匹配信息,然后在上面线段树合并(当然SAM可)

有2操作?

维护时间的时候带上一点技巧qwq???

tyy提出广义后缀自动机做法

离线,然后就真的类似一个trie树的方法全部离线把这长度和为n2n^2n2的串建出来

BFS的方法建树?相当于对于trie我们直接记录建立到节点x当前的lst指针指向哪里

然后我们就可以BFS的方法每次基于上一侧父亲的lst建树

然后再去跑匹配,统计一个树上endpos和就好了

例题 13−3:

(题目来源:我的口胡)String Master XXL

维护一个初始为空的字符串 S,支持 n 次操作,操作为:

  • 末尾插入;

  • 首端插入;

  • 给定字符串 T 查询其在当前 S 中的出现次数。

    $n,\sum |T| $同阶。

做法就是维护三部分串,然后第一部分和第二部分都是前面插入/后面插入字符,可以reverse变成后尾插入然后reverse匹配

中间拼起来的部分不会超过2T2T2T个节点

例题 13−4:

(题目来源:我的口胡)String Master XXXL

维护一个初始为空的字符串 S,支持 n 次操作,操作为:

  • 末尾插入;
  • 末尾删除;
  • 首端插入;
  • 首端删除;
  • 给定字符串 T 查询其在当前 S 中的出现次数。

强制在线。

n,∑∣T∣n,\sum |T|n,T 同阶。

哈希

维护长度为$1…\sqrt n $ 子串的哈希值出现次数

然后匹配的时候直接匹配

对于T长度≥n\geq \sqrt nn我们可以暴力跑一遍KMP

Bouns改成单点加入字符单点删除字符?

考虑还是维护长度前B的子串哈希值

那么改掉一个点,最多影响B2B^2B2个哈希值

于是我们有O(nB2+n/B∗n),B=n3O(nB^2+n/B*n),B=\sqrt[3] nO(nB2+n/Bn),B=3n最优为O(n53)O(n^{\dfrac{5}{3}})O(n35)

sam做法有一个很好地重构方法:

维护开头和结尾两个栈

当一个栈被清空时,就重构整个结构,将另一个栈中的元素平分后再作为新的两个栈。

性质:长度为 n 的字符串的本质不同非空回文子串数量不超过 n。

假设我们对于某个前缀只看以他结尾最长的回文子串,那么会发现这样的回文子串只有O(n)O(n)O(n)个,而且对于将整个序列最长的回文子串变成了若干段等差数列

所以以每个本质不同回文子串都会在他最靠前出现的时候被算入一次就不超过nnn个了

性质:回文串的 Border 是回文串,且一个点对应的回文子串的最长回文 Border 是它在回文树上的失配指针对应的回文串。

回文子串出现次数:统计 S 的每个本质不同回文子串的出现次数。

回文子串匹配:给定 S,T,求 T 的每个前缀的最长的是 S 子串的回文后缀。

例题 15:

给定 m 个字符串S1,…,SmS_1,\ldots,S_mS1,,Sm,求最长公共回文子串。

求匹配然后取min就好了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值