字符串算法复习1
KMP
应用:定义\(nxt[i]\)表示字符串\(S\)的子串\(S_0,\cdots,S_i\)中的最长公共真前后缀(不包括串本身),即\(S_0,\cdots, S_{nxt[i]-1}=S_{i-nxt[i]+1},\cdots,S_i\)
时间复杂度: \(O(n)\)
模板
vector<int> KMP(string s){
int n=s.size();
vector<int>nxt(n);
for(int i=1,j=0;i<n;i++){
j=nxt[i-1];
//比较的是S_i和S_{nxt[i-1]-1+1},而S_{i-1}=S_{nxt[i-1]-1}
while( j>0 && s[i]!=s[j]) j=nxt[j-1];
j+=(s[i]==s[j]);
nxt[i]=j;
}
}
扩展KMP算法(Z算法)
应用:定义\(z[i]\)表示字符串\(s[0..n-1]\)和\(s[i...n-1]\)(即以\(s[i]\)开头的后缀)的最长公共前缀(\(LCP\))的长度
时间复杂度:\(O(n)\)
原理:
维护右端点最靠右的匹配段,定义\([l,r]\)为在枚举到\(i\)之前,右端点最靠右的\(s[l..r]=s[0,r-l]\)
当枚举到\(i\)时:
- 如果\(i<=r\),有\(s[i,r]=s[i-l,r-l]\),于是\(z[i]\ge min(z[i-l],r-i+1)\)
这时:如果\(z[i-l]<r-i+1\),则\(z[i]=z[i-l]\),否则令\(z[i]=r-i+1\),然后暴力枚举下一个字符扩展\(z[i]\)直
到不能扩展为止
- 如果\(i>r\),直接暴力求出\(z[i]\)
- 在求出\(z[i]\)后,如果\(i+z[i]-1>r\),更新\(l=i,r=i+z[i]-1\)
扩展:如果要求\(S\)的每一个后缀和\(T\)的最长公共前缀,那么可以令字符串\(S^{'}=T+'\#'+S\),跑一遍\(Z-algorithm\)即可
const int N;
int z[N];
string s;
for(int i=1,l=0,r=0;i<n;i++)
{
if(i<=r && z[i-l]<r-i+1) z[i]=z[i-l];
else z[i]=max(0,r-i+1);
while(i+z[i]<n&&s[z[i]]==s[i+z[i]]) ++z[i];
if(i+z[i]-1>r) l=i,r=i+z[i]-1;
}
Manacher 算法
时间复杂度:\(O(n)\)
应用:给定一个字符串,找到所有对\((i,j)\)使得子串\(s[i...j]\)为一个回文串
定义:\(d1[i]\)表示以\(i\)为中心的最长奇回文串半径,比如\(abcba\),其中\(d1[3]=3\),于是回文串长度为\(2 d1[i]-1\)
\(d2[i]\)表示以\(i\)为中心的最长偶数回文串半径,比如\(abba\),其中\(d1[3]=2\),于是回文串长度为\(2 d2[i]\)
int d1[N],d2[N];//d1求奇数回文串,d2求偶数回文串
for(int i=0,l=0,r=-1;i<n;i++)
{
int k=(i>r)?1:min(d1[ l+r-i ],r-i+1);
while(0<=i-k&&i+k<n&&i+k<n&&s[i-k]==s[i+k]) k++;
d1[i]=k--;
if(i+k>r) l=i-k,r=i+k;
}
for(int i=0,l=0,r=-1;i<n;i++)
{
int k=(i>r)?0:min(d2[l+r-i+1],r-i+1);
while(0<=i-k-1&&i+k<n&&s[i-k-1]==s[i+k]) k++;
d2[i]=k--;
if(i+k>r) l=i-k-1,r=i+k;
}
奇偶统一处理
此时的\(d[i]\)表示以\(i\)为中心的最长回文串的长度\(-1\)
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int d[N],n;
char s0[N],s[N];
int main()
{
scanf("%s",s0);
s[n]='#';
for(int i=0;i<strlen(s0);i++) s[++n]=s0[i],s[++n]='#';
for(int i=0,l=0,r=-1;i<=n;i++)
{
int k=i>r?1:min(d[l+r-i],r-i+1);
while(0<=i-k&&i+k<=n&&s[i-k]==s[i+k]) k++;
d[i]=k--;
if(i+k>r) l=i-k,r=i+k;
}
for(int i=0;i<=n;i++)
if(s[i]!='#') cout<<d[i]-1<<endl;
}
最小表示法
- 时间复杂度:\(O(n)\)
- 循环同构:当字符串\(S\)中可以选定一个位置\(i\)满足\(S[i...n]+S[1...i-1]=T\),则称\(S\)与\(T\)循环同构
- 应用:求与字符串\(S\)循环同构的所有字符串中字典序最小的字符串
string s;
int n=s.size();
int k=0,i=0,j=1;
while(k<n&&i<n&&k<n)
{
if(s[(i+k)%n]==s[(j+k)%n]) k++;
else
{
if(s[(i+k)%n]>s[(j+k)%n]) i=i+k+1;
else j=j+k+1;
if(i==j) i++;
k=0;
}
}
i=min(i,j);
for(int p=i;p<n;p++) cout<<s[p];
for(int p=0;p<i;p++) cout<<s[p];
puts("");