字符串专题
字符串
前言
假期的时候刷了点字符串的专题,感觉板子变得更加普适和完善了
KMP
kmp算法实际上就是找最长公共前后缀,之前一直都是用的acwing的板子,根据董晓算法的板子和自己写题的习惯完善了一下。
kmp还可以用来找循环节
板子
bool KMP(string s, string t){
int n = s.size(), m = t.size();
vector<int>ne(n+1);
s = "?" + s;
t = "?" + t;
for(int i = 2, j = 0;i <= m;++i){
while(j && t[i] != t[j+1]) j = ne[j];
if(t[i] == t[j+1]) j++;
ne[i] = j;
}
for(int i = 1, j = 0;i <= n;++i){
while(j && s[i] != t[j+1]) j = ne[j];
if(s[i] == t[j+1]) j++;
if(j == m){
j = ne[j];
return true;
}
}
return false;
}
EXKMP
exkmp可以用来某个串的任意后缀的最长公共前缀的长度,称为z函数,如果是两个不同的串就叫p函数。
实际上我觉得exkmp和manacher在代码上比较像。
板子
/*
求字符串s的任意后缀的最长公共前缀(LCP)的长度
利用逆串和原串做p函数还可以得到一个串的后缀/前缀是否为回文串
*/
/*
求s串的LCP
*/
vector<int> get_z(string s){
int n = s.size();
s = "?"+s;
vector<int>z(n+1);
z[1] = n;
for(int i = 2, l, r = 0;i <= n;++i){
if(i <= r) z[i] = min(z[i-l+1], r-i+1);
while(i+z[i]<=n && s[1+z[i]]==s[i+z[i]]) z[i]++;
if(i+z[i]-1>r) l = i, r = i+z[i]-1;
}
return z;
}
/*
求s串(作为前缀)和t串(作为后缀)的LCP
*/
vector<int> get_p(vector<int>z, string s, string t){
int n = s.size(), m = t.size();
s = "?"+s;
t = "?"+t;
vector<int>p(m+1);
for(int i = 1, l, r = 0;i <= m;++i){
if(i <= r) p[i] = min(z[i-l+1], r-i+1);
while(1+p[i]<=n && i+p[i]<=m && s[1+p[i]]==t[i+p[i]]) p[i]++;
if(i+p[i]-1 > r) l = i, r = i+p[i]-1;
}
return p;
}
manacher
线性时间内找到每个点的最长回文半径,很多回文串的题整点manacher就能做
板子
string preProcess(string a){
int n = a.size();
string s = "?#";
for(int i = 0;i < n;++i){
s += a[i];
s += "#";
}
return s;
}
vector<int> get_d(string s, int n){
vector<int>d(n+1);
d[1] = 1;
for(int i = 2, l, r = 1;i <= n;++i){
if(i <= r) d[i] = min(d[r-i+l], r-i+1);
else d[i] = 1;
while(s[i-d[i]]==s[i+d[i]]) d[i]++;
if(i+d[i]-1 > r) l = i-d[i]+1, r = i+d[i]-1;
}
return d;
}
void solve(){
string s;cin >> s;
string str = preProcess(s);
int n = str.size()-1;
vector<int>d = get_d(str, n);
cout << *max_element(all(d))-1 << endl;
/*
得到原串的L R区间(从1开始计数)
*/
// for(int i = 1;i <= n;++i){
// if(d[i]-1 > r-l+1){
// l = i/2-(d[i]-1)/2;
// r = i/2+(d[i]-1)/2;
// if(i%2){
// l++;
// }
// }
// }
}