字符串学习笔记
1. manacher 马拉车
思路
- 回文串长度有奇有偶,奇回文串有对称中心,那偶回文串呢,所以我们就在每两个字符串之间加一个#,再在整个串前面加一个 % ,这样奇偶串就可以用按一种方法处理了,比如abcd --> %a#b#c#d#
- 我们可以用O(n) 求出r数组,r[i]是以i为对称中心的回文串半径(长度包括i),令mx是回文串覆盖的最右边界的右边一位,p是覆盖最右边界的回文串的对称中心,这时我们要求r[i],暴力会TLE,当p<mx时,我们考虑r[i]可以从r[2 * p - i] 处已确认对称的部分转移过来,r[i]=min (mx-p,r[ 2 * p - i ]),之后继续尝试暴力向外拓展,看r[i]是否能增大
code
#include<bits/stdc++.h>
#define N 22000010
#define mod 998244353
#define int long long
using namespace std;
int n,ans;
int r[N];
char a[N];
template<class T> inline void read(T &x)
{
x=0;int g=1;char s=getchar();
for (;s<'0'||s>'9';s=getchar()) if (s=='-') g=-1;
for (;s>='0'&&s<='9';s=getchar()) x=(x<<1)+(x<<3)+(s^48);
x*=g;
}
signed main()
{
int i,j,op,x,y,z;
scanf("%s",a+1);
n=strlen(a+1);
for (i=n;i;i--) a[2*i]=a[i],a[2*i+1]='#';a[1]='#';a[0]='$';
int p=1,mx=1;
n=2*n+1;
for (i=1;i<=n;i++)
{
if (mx<=i) r[i]=1;
else r[i]=min(mx-i,r[2*p-i]);
while(a[i+r[i]]==a[i-r[i]]) r[i]++;
if (i+r[i]>mx)
{
mx=i+r[i];p=i;
}
ans=max(ans,r[i]);
}
printf("%lld\n",ans-1);
return 0;
}
2. kmp
思路
kmp时间复杂度小的精髓在于,每次失配以后,不是从头开始重新匹配,而是用一段尽量长的与已匹配部分后缀相同的模式串前缀与文本串的已匹配部分匹配,并且尝试继续向后拓展,如果又一次失败,那就换成较小的前缀去尝试拓展
我们要先求出kmp数组,就要自己匹配自己,kmp[i]是以i结尾的后缀相等的最长前缀的右端点,再用求出的kmp数组去求出每个模式串在文本串里出现的位置
code
#include<bits/stdc++.h>
#define int long long
#define N 2000010
#define re register
using namespace std;
int n,m,ans,kmp[N];
char a[N],b[N];
template <class T> inline void read(T &x)
{
x=0;int g=1;char s=getchar();
for (;s<'0'||s>'9';s=getchar()) if (s=='-') g=-1;
for (;s>='0'&&s<='9';s=getchar()) x=(x<<1)+(x<<3)+(s^48);
x*=g;
}
signed main()
{
re int i,j,x,y,z,op;
scanf("%s",a+1);n=strlen(a+1);
scanf("%s",b+1);m=strlen(b+1);
j=0;
for (i=2;i<=m;i++)
{
while(j&&b[i]!=b[j+1]) j=kmp[j];
if (b[i]==b[j+1])j++;
kmp[i]=j;
}
j=0;
for (i=1;i<=n;i++)
{
while(j&&a[i]!=b[j+1]) j=kmp[j];
if (a[i]==b[j+1]) j++;
if (j==m)
{
printf("%lld\n",i-m+1);
j=kmp[j];
}
}
for (i=1;i<=m;i++) printf("%lld ",kmp[i]);
return 0;
}