专栏文章
笔记 · 一些字符串
个人记录参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @miohtfat
- 此快照首次捕获于
- 2025/12/02 19:26 3 个月前
- 此快照最后确认于
- 2025/12/02 19:26 3 个月前
KMP
KMP算法可以在 的时间复杂度内完成串的匹配。
其核心思想在于 数组(也叫 )。 的意义是:前缀 最长相同的真前缀和真后缀的长度。
例如
abcabb, 的值为 (ab)那这有什么用呢?考虑匹配长度为 的文本串 和长度为 的模式串 。我们先对 求出 。
在失配时,暴力做法是一位一位移动重新尝试,然而KMP算法是这样处理的:枚举文本串的位置 。
- 如果此时考虑到模式串的第 位( 位是匹配上的),而 与 失配。这时令 ,相当于把模式串向后移动了一段位置,使得前缀覆盖了后缀那段相同的位置,跳过了中间必然不可能匹配的位置,这就是KMP的核心思想。如果接下来 还是失配,就再重复(注意 就不能再往前跳了)。
- 如果 和 成功匹配上了,那么简单的
i++,j++即可。 - 如果
j==m,说明匹配成功。此时我们要想找出所有的匹配,就得先让其失配,。
好了,那现在问题就变成了怎么求出 数组。我们考虑让模式串自己匹配自己。这也是很多人(包括我)初学kmp最不能理解的一点。
可以想象将模式串复制一份,错位开来。
CPPabcabc
abcabc
我们现在要匹配这两个串。用 枚举第一个串, 表示第二串匹配到哪了。考虑类似的匹配过程,现在要求 。
- 如果 和 失配,我们还能令 吗?答案是可以的,因为 , 已经求出过。这一步是一样的。
- 如果 和 成功匹配上,简单的
i++,j++即可。 - 以上过程结束后,第二个串的前缀就匹配到了第一个串的后缀,并且是最长的。而这正是 数组的意义,赋值
nxt[i]=j即可。
实际操作中,先令
nxt[1]=0,用两个指针在模式串上跑就行了。Manacher
Manacher算法可以在 的时间复杂度内求解出以每个位置为中心的最长回文半径 。
经典问题:求最长回文子串长度(求最长回文直径,两个是一样的)
先考虑这件事情暴力怎么求。光枚举每个字符作为中心是不行的,因为偶数长度的回文子串的中心在两个字符之间。所以我们先在两两字符之间插入辅助字符,变成一个长度为 的字符串,然后从左往右枚举每个字符,向两边扩展,这样做是 的。
如何优化呢?我们考虑求出了最长回文半径,即可求出最长回文直径了。考虑记录两个值:
:对于此前考虑过的所有位置,以它们为回文中心最右能扩展到的位置。
:具体是哪个位置,把它作为回文中心后,让右边界成功扩展到了 。
设 关于 的对称点为 ,显然 是一个大回文串。
假如现在考虑到位置 。如果 ,那不好意思,只能暴力扩充了。
如果 呢?
考虑回文串的性质:关于【回文中心】对称的两个串,实际上是全等的,只不过一个是正序,一个是逆序。那么我们可以尝试借助 关于 的对称点 ,借助 确定 的下限,在此基础上进行扩展。
- 如果 的回文区域全部在 内,那么 的回文区域就和 的完全一致了,自然有 。
- 如果 的回文区域左端到达甚至超出了 ,因为 右边的区域暂时还不明朗,所以我们只能说, 的回文区域的右边界也一定不小于 ,即 ,这时就需要暴力扩充了。
以上步骤可以形式化如下:
-
对于每个 ,如果它不在右边界范围内,直接进入下一步;否则就可以先给出一个下限 。
-
然后在下限的基础上尝试向两边暴力扩充。这里有个细节,那就是提前让 号位置为一个和所有字符都不一样的字符,这样扩充到了 就一定会停止了。
-
现在确定了 ,再将新的右边界 和 进行比较,尝试更新 与 。
时间复杂度证明:右边界 一定是单调递增的,最多往右移动 次。
P3805 Manacher模版题
求解过程不再赘述,和上述一致。
注意一下答案的更新。现在求出的回文半径 是带有特殊字符的。可以证明 一定为偶数,且每 个字符有一半是特殊字符,实际上的回文半径为 ,则回文直径为 。
Code
CPP#include <bits/stdc++.h>
using namespace std;
const int N=110000005;
char s[N<<1];
int n,p[N<<1],ans=0;
void read(){
char c=getchar();
s[0]='~',s[n=1]='|';
while(c<'a'||c>'z') c=getchar();
while(c>='a'&&c<='z'){
s[++n]=c;
s[++n]='|';
c=getchar();
}
}
int main(){
read();
for(int i=1,r=0,mid=0;i<=n;i++){
if(i<=r) p[i]=min(p[(mid<<1)-i],r-i+1);
while(s[i-p[i]]==s[i+p[i]]) ++p[i];
if(p[i]+i>r){
r=p[i]+i-1;
mid=i;
}
ans=max(ans,p[i]-1);
}
cout<<ans<<endl;
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...