基础字符串(KMP、Manacher、……)

djwj233 基础字符串

ix35 的字符串复习

概念与定义

基础概念

  • 拼接操作可被记为 \(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[i]=\max_{k=0}^{k\le i}{\{k:s[1\dots k]=s[i-k+1\dots i]\}} \]

特别的, \(pre[1]=0\)

KMP 就是两个字符串匹配是,通过前缀函数优化实现 \(O(n)\) 转移。

P3375 【模板】KMP字符串匹配

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)\) 就是最小表示法的起始位置。

P1368 【模板】最小表示法

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\)

posted @ 2021-10-29 20:15  EricQian06  阅读(91)  评论(0编辑  收藏  举报