以下内容适合已经掌握KMP算法的同学观看。
KMP应用一:字符串匹配
这是KMP最常见的应用,我就不详细解释。贴一道模板题,
洛谷3375
AC代码
//字符串匹配模板
#include<stdio.h>
#include<cstring>
#define maxn 1000010
char s[maxn],t[maxn];
//NEXT是用于字符串匹配的优化版:优化了模式串为aaa...aaa.a这种情况
//NEXT是正常版本,用处更广
int NEXT[maxn],NEXT2[maxn];
int lent,lens;
inline void get_NEXT(){
int i=0,j=-1;
NEXT2[0]=NEXT[0]=-1;
while (i<=lent){
while(t[i]!=t[j] && j!=-1)
j=NEXT[j];
if(t[i+1]==t[j])
NEXT[++i]=NEXT[j++];
else
NEXT[++i]=++j;
NEXT2[i]=j;
}
}
inline void cmp(){
int i=0,j=0;
while (i<lens){
while(s[i]!=t[j] && j!=-1)
j=NEXT[j];
i++,j++;
if(j==lent)
printf("%d\n",i-lent+1);
}
}
int main()
{
scanf("%s%s",s,t);
lent=strlen(t),lens=strlen(s);
get_NEXT();
cmp();
for(int i=1;i<lent;i++)
printf("%d ",NEXT2[i]);
printf("%d\n",NEXT2[lent]);
return 0;
}
KMP应用二:KMP与树的联系
先通过这个题目了解一下与KMP相关的两个名词(border,k前缀)的定义。
kmp就是通过求出每个k前缀的最大border(用next数组记录)来减少匹配次数。
border性质一
传递性:给出长度关系为(u<v<x)的三个字符串。那么如果字符串u是v的border,v是x的border,那么u也是x的border。画图易得。
如ab(u) 是 ab----ab(v)的前缀,而ab----ab是ab----ab------ab----ab(x)的前缀(‘-’代表未知字符),那么显然ab是ab—ab------ab—ab的前缀
另外,给出长度关系为(u<v<x)的三个字符串。那么如果字符串u是x的border,v是x的border,那么u也是v的border。同样画图易得。
但是知道结论1、3不能直接推出2.
利用这个性质可以得出,设串str有三个border,且函数F(u)代表u的最大border,且称u是F(u)的父亲,F(u)是u的儿子。
那么F(u),F(F(u)),F(F(F(u)))即为str的三个border。
那么str的所有border可以按照父子关系连成一条链。
再来考虑next数组,我们可以发现,每个k前缀拥有唯一的max_border,而每个max_border又是一个k前缀,这显然是一个树的结构。我们把用next数组建成的这棵树称为失配树
经过上述讨论不难发现,每个k前缀的所有border就是树中其最大border到根节点这条路径上的点。
由此可以得出上面这题的实质就是求p前缀的max_borderp与q前缀的max_borderq的LCA。
由于这题时间卡的比较死,不能暴力求lca,我选择了用树上倍增来优化求LCA。
提交地址——洛谷P5829
AC代码
//kmp失配树,倍增求lca
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
char s[maxn];
int lens;
int dep[maxn],fa[maxn][23];
void get_nex(){
int i=0,j=-1;
//fa[i][0]替代了next[i]
dep[0]=0;fa[0][0]=-1;
while (i<=lens){
while (s[i]!=s[j] && j!=-1)
j=fa[j][0];
dep[++i]=dep[++j]+1;
fa[i][0]=j;
//递推倍增父亲
for(int k=1;k<=22;k++)
fa[i][k]=fa[fa[i][k-1]][k-1];
}
}
//树上倍增求lca
int get_border(int x,int y){
if(dep[x]>dep[y])
swap(x,y);
//先使高度一致
for(int i=22;i>=0;i--){
if(dep[fa[y][i]]>=dep[x])
y=fa[y][i];
if(x==y)
return x;
}
//一起往上跳,但保证不相遇
for(int i=22;i>=0;i--){
if(fa[y][i]!=fa[x][i])
y=fa[y][i],x=fa[x][i];
}
return fa[x][0];
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>s;
lens=strlen(s);
get_nex();
int m;
cin>>m;
while (m--){
int x,y;
cin>>x>>y;
cout<<get_border(fa[x][0],fa[y][0])<<endl;
}
return 0;
}
KMP应用三:求字符串周期
border性质二:
字符串s减去s的一个border即可得到s的一个周期。
贴一道关于这个应用的模板题
洛谷3435
AC代码:
#include<bits/stdc++.h>
using namespace std;
char s[1000006];
int nex[1000006];
int n;
long long ans=0;
void get_nex()
{
int i=0,j=-1;
nex[0]=-1;
while (i<=n)
{
while (s[i]!=s[j] && j!=-1)
j=nex[j];
nex[++i]=++j;
//题目求最大周期,所以要找最小border,同时起到压缩路径的作用
while (nex[i] && nex[nex[i]])
nex[i]=nex[nex[i]];
if(nex[i]>0)
ans+=i-nex[i];
}
}
int main()
{
cin>>n;
cin>>s;
get_nex();
cout<<ans<<endl;
return 0;
}
KMP应用四:求长度小于等于原字符串长度一半的前缀
题解:
//pre[i]:s串i前缀的最长不重叠前缀
//pre[i]<=i/2,pre[i+1]<=i/2+1
//所以pre[i+1]<=pre[i]+1
//所以要求从pre[i]+1开始往前寻找即可得到pre[i+1]的值
//失配树中每个点的深度即代表往前有几个border
AC代码
洛谷2375
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+6,mod=1e9+7;
char s[maxn];
int nex[maxn],len,pre[maxn];
//pre[i]:s串i前缀的最长不重叠前缀
//pre[i]<=i/2,pre[i+1]<=i/2+1
//所以pre[i+1]<=pre[i]+1
//所以要求从pre[i]+1开始往前寻找即可得到pre[i+1]的值
vector<int> V[maxn];
void get_nex()
{
int i=0,j=-1;
nex[0]=-1;
while (i<=len)
{
while (s[i]!=s[j] && j!=-1 )
j=nex[j];
nex[++i]=++j;
V[j].push_back(i);
int x=i;
}
}
void get_pre()
{
int i=0,j=-1;
pre[0]=-1;
while (i<=len)
{
while (s[i]!=s[j] && j!=-1 || (j+1)>(i+1)/2)
j=nex[j];
pre[++i]=++j;
}
}
int dep[maxn];
//失配树中每个点的深度即代表往前有几个border
void dfs(int x,int fa)
{
dep[x]=dep[fa]+1;
for(int i=0;i<V[x].size();i++)
{
int v=V[x][i];
if(v!=fa)
dfs(v,x);
}
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int n;
cin>>n;
while (n--)
{
cin>>s;
len=strlen(s);
for(int i=0;i<=len;i++)
V[i].clear();
get_nex();
get_pre();
dep[0]=-1;
dfs(0,0);
long long ans=1;
for(int i=2;i<=len;i++)
ans=ans*(dep[pre[i]]+1)%mod;
cout<<ans<<'\n';
}
return 0;
}