KMP系列题目。
1.KMP最常用的用法:查找一个字符串在另一个字符串中的位置。复杂度O(m+n)
P3375模板题
下面上代码:
#include<bits/stdc++.h>
using namespace std;
string a,b;
const int N=1e6+5;
int f[N];
void fun(string a){
int l=a.size(),j=0;
for(int i=1;i<l;i++)
{
while(j&&a[i]!=a[j]) j=f[j-1];
if(a[i]==a[j]) j++;
f[i]=j;
}
}
void kmp(string a,string b){//a与b进行kmp匹配
fun(b);
int l=a.size(),lb=b.size(),j=0;
for(int i=0;i<l;i++)
{
while(j&&a[i]!=b[j]) j=f[j-1];
if(a[i]==b[j]) j++;
if(j==lb)
{
cout<<i-lb+2<<endl;//输出子串在字符串中的位置. (这里答案下标要求为1开始所以是+2)
j=f[j-1];//回退 重新匹配
}
}
for(int i=0;i<lb;i++)
printf(i==lb-1?"%d\n":"%d ",f[i]);
}
int main(){
cin>>a>>b;
kmp(a,b);
return 0;
}
用法2:回文串的查找(用于从字符串头开始或着到尾结束的字符串十分方便)
不适用于中间是回文串的字符串,比如:ABCBD。
例题:CF1325D. Prefix-Suffix Palindrome (Hard version)
题意:给定字符串,求由字串前缀和后缀组成的最大回文字符串,且长度不大于字符串本身.
思路1:对于easy version :可以选择暴力。双向指针找到最大相同前后缀strL,strR。在对中间的一段字符串进行暴力枚举找到最长的回文串mid。最后的答案为 strL+mid+strR 下面上代码
#include<bits/stdc++.h>
using namespace std;
int t,l,r;
string s,A;
bool C(string s)
{
for(int i=0;i<s.size()/2;i++)
if(s[i]!=s[s.size()-1-i])
return false;
return true;
}
int main()
{
cin>>t;
while(t--&&cin>>s)
{
if(C(s))
{
cout<<s<<'\n';
continue;
}
l=0,r=s.size()-1;
while(l<r&&s[l]==s[r])
l++,r--;
A="";
for(int i=1;i<=r-l;i++) //找到中间的回文串 因为之前已经判断出不可能全部为回文串所以 i最大为r-l 而不是r-l+1
if(C(s.substr(l,i)))
A=s.substr(l,i);
else if(C(s.substr(r-i+1,i)))
A=s.substr(r-i+1,i);
for(int i=0;i<l;i++)
cout<<s[i];
cout<<A;
for(int i=r+1;i<s.size();i++)
cout<<s[i];
cout<<'\n';
}
return 0;
}
思路2:对于hard version 由于数据非常大 。所以对于mid的求法需要优化。这里用到KMP,方法为将
中间的字符串mid翻转过来 然后变成 mid+#+rev 利用KMP求出与前缀相连的最大回文串。
再求rev+#+mid 求出于后缀相连的最大回文串
举个例子,假设mid=ABACDD ,prv1=ABA,prv=DD,
mid+#+rev=ABACDD#DDCABA 最大前后缀为3所以最大回文串长度为3ABA,
rev+#+mid=DDCABA#ABACDD 最大前后缀为2所以最大回文串长度为2DD 下面上代码.
类似于将字符串的第一个与最后一个匹配,第二个与倒数第二个匹配……
#include <bits/stdc++.h>
using namespace std;
int pi[2000001]; //最长公共前后缀表(pi[i]表示字符串s从0到i的最长公共前后缀 那一部分一定是回文的
string preF(string s) { //求最长公共前后缀. 举个例子 dfdce 变形为 dfdce#ecdfd 最长公共前后缀为dfd
int n = (int)s.size(), j = 0;//显然pi[0]=0 //ecdfd#dfdce 最长公共前后缀为1
for (int i = 1; i < n; ++i) {//求pi[1]到pi[n-1] //i为后缀的最后一个下标 从字符串的第2个字母开始
while (j && s[i] != s[j]) { //这里的j既是当前最大的匹配长度,又是当前 前缀的最后一个下标.
j = pi[j - 1]; //j退回到前一个字符的最大匹配长度
}
if (s[i] == s[j]) {
++j;
}
pi[i] = j;
}
/*for(int i=0;i<n;i++) printf("pi[%d]=%d\n",i,pi[i]); //printf("j=%d\n",j); */
return s.substr(0, j);
}
int main() {
ios_base::sync_with_stdio(0);
cin.tie(0);
int t;
cin >> t;
while (t--) {
string s;
cin >> s;
int n = s.size();
int j = n - 1, i = 0;
while (i < j && s[i] == s[j]) {
++i;
--j;
}
string mid = s.substr(i, j - i + 1); //中间的字符串.
string rev = mid;
reverse(rev.begin(), rev.end());//翻转过来
string ans1 = preF(mid + '#' + rev);
string ans2 = preF(rev + '#' + mid);
//cout<<"mid="<<mid<<endl;
//cout<<"rev="<<rev<<endl;
//cout<<"mid#rev="<<mid<<"#"<<rev<<endl;
//cout<<"ans1="<<ans1<<endl;
///cout<<"ans2="<<ans2<<endl;
cout << s.substr(0, i) << (ans1.size() > ans2.size() ? ans1 : ans2) << s.substr(j + 1) << '\n';
}
}
用法3:求字符串的最小循环节和循环周期.
结论:最小循环长度为 n-f[n] f[i]为最大前后缀表。注意这里的循环不是一定满足严格循环abcabcabc式的,bcabcabc也看作是循环的,最小循环节长度也为3(abc)
这里的结论不作证明。下面上代码。
从0开始的字符串
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int f[N],n,j;
int main(){
string a;
cin>>n>>a;
for(int i=1;i<n;i++)
{
while(j&&a[j]!=a[i]) j=f[j-1];
if(a[j]==a[i]) j++;
f[i]=j;
}
cout<<n-f[n-1]<<endl;
printf("循环周期为%d\n",n/(n-f[n-1]));
return 0;
}
从1开始的字符串
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int f[N],j,n;
char a[N];
int main(){
f[1]=0;
cin>>n>>(a+1);
for(int i=2;i<=n;i++)
{
while(j&&a[i]!=a[j+1]) j=f[j];
if(a[i]==a[j+1]) j++;
f[i]=j;
}
cout<<n-f[n]<<endl;
printf("循环周期为%d\n",n/(n-f[n]));
return 0;
}