基础字符串(KMP、Manacher、……)
概念与定义
基础概念
- 拼接操作可被记为 \(C=A+B\),也可写作 \(C=AB\)。
- 记一个字符串 \(S\) 的反串为 \(S^R\),那么 \(S\) 为回文串当且仅当 \(S=S^R\)。注意:\(\epsilon\) 也是回文串。
周期结构
-
定义 \(x\) 是 \(S\) 的周期(Period),当且仅当 \(x\le |S|\) 且 \(\forall i\in[1,|S|-x],\ S_i=S_{i+x}\)。
若 \(x\) 整除 \(|S|\),则称 \(x\) 是 \(S\) 的一个整周期。
-
定义 \(T\) 是 \(S\) 的 Border,当且仅当 \(T\) 同时是 \(S\) 的真前缀和真后缀。
同时也称 \(|T|\) 是 \(S\) 的 Border。
容易看出 \(|S|\) 是任意字符串 \(S\) 的平凡周期,\(0\) 或 \(\epsilon\) 是其平凡 Border。
Border 的性质及证明
- \(p\) 是 \(S\) 的周期,当且仅当 \(|S|-p\) 是 \(S\) 的 Border。(易证,KMP 用这个性质在 \(O(n)\) 的复杂度内求出周期)
- 咕咕咕(见 djwj233 博客)
前缀函数和 KMP
前缀函数定义为字符串每一个前缀的 Border 长度,即:
特别的, \(pre[1]=0\) 。
KMP 就是两个字符串匹配是,通过前缀函数优化实现 \(O(n)\) 转移。
for(int i=2,j=0;i<=m;i++)
{
while(j>0 && s[j+1]!=s[i]) j=pre[j];
if(s[j+1]==s[i]) j++;
pre[i]=j;
}
for(int i=1,j=0;i<=n;i++)
{
while(j>0 && (j==n || s[j+1]!=t[i])) j=pre[j];
if(s[j+1]==t[i]) j++;
if(j==m) printf("%d\n",i-j+1),j=pre[j];
}
最小表示法
最小表示法是指字符串 \(S\) 所有循环同构的字符串中字典序最小的一个。
一种解法是 SAM,另一种是用双指针乱搞:
- 首先将 \(s\) 倍长一遍来实现循环。
- 规定两个指针 \(i,j\),一开始指向 \(s_1,s_2\)。
- 用 \(k\) 表示当前以 \(i,j\) 为起点的两个字符串最长公共前缀的长度。
- 一直执行以下操作直到 \(i,j\) 中任意一个 \(>n\)。
- 如果 \(s_{i+k+1}<s_{j+k+1}\),\(j=j+k+1\)。
- 如果 \(s_{i+k+1}>s_{j+k+1}\),\(i=i+k+1\)。
- 如果 \(i=j\),任意一个加一。
最终 \(\min(i,j)\) 就是最小表示法的起始位置。
Manacher 内容及实现
Manacher 用于一个字符串的回文串的匹配,利用对称的性质实现优化。
我们记下当前最右边的已经扩展到的位置 right_most
,以及这个扩展位置的重心点 midpos
,如果我们的 \(i\le right\_most\),那么可以直接将对称的部分从 \(i\) 关于 \(midpos\) 的对称点直接拉过来用。
这样可以证明复杂度是 \(\mathcal{O(n)}\) 级别的。
#define Maxn 500005
int nt,right_most,midpos;
int len[Maxn];
char s[Maxn],t[Maxn<<1];
scanf("%s",s+1),n=strlen(s+1),nt=n<<1|1;
for(int i=1;i<=n;i++) t[(i<<1)-1]='#',t[i<<1]=s[i];
t[n<<1|1]='#',t[0]='@',t[nt+1]='$';
for(int i=1;i<=nt;i++)
{
if(right_most>i) len[i]=min(right_most-i+1,len[midpos+midpos-i]);
else len[i]=1;
while(t[i+len[i]]==t[i-len[i]]) len[i]++;
if(i+len[i]-1>right_most) right_most=i+len[i]-1,midpos=i;
ans=max(ans,len[i]);
}
失配树
通过失配关系实现前缀之间的继承情况,形成一棵 \(Fail\) 树,通俗理解就是将 KMP 的每个节点当做树上的一个节点,即 KMP 上树。
两道模板题:P5829 【模板】失配树,CF432D Prefixes and Suffixes。
P2375 [NOI2014] 动物园
这道题可以根本不用往失配树去理解,我们发现每一次最长合法 Border 的长度最多只会增加 \(1\),那么直接暴力跑 KMP 并及时处理 Border 长度大于一半的情况即可。
一些正确性证明:如果这个位置的 Border 长度大于了 \(\frac{i}{2}\),这个 Border 的后缀起始点将永远不再会成为答案,因此跳出这个 Border 一定是对的。
P3426 [POI2005]SZA-Template
考虑设 \(dp(i)\) 表示主串的前缀 \([1,i]\) 需要的最短长度。
容易发现在失配树上 \(dp(i)\) 沿着树成单调递增关系,考虑 \(dp(i)\) 与 \(dp(nex_i)\) 的关系。
如果 \(dp(i)\in (dp(nex_i),nex_i]\),由于 \([1,i]\) 的一个 Border 为 \(dp(nex_i)\),所以 \(dp(nex_i)\) 也一定能够生成这个串。
所以 \(dp(i)\) 能够生成的串一定是 \(dp(nex_i)\) 能够生成的真子集,即答案取 \(dp(nex_i)\) 一定比 \((dp(nex_i),nex_i]\) 优。
因此我们只用判断能否由 \(dp(nex_i)\) 生成 \([1,i]\) 即可,具体即记录 \(dp(nex_i)\) 能够生成的最长串 \(s\),如果 \(|s|+|dp(nex_i)|\ge i\) 即可。
如果上述情况不成立,意味着 \(dp(i)=i\)。