KMP专题

以下内容适合已经掌握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----abab----ab------ab----ab(x)的前缀(‘-’代表未知字符),那么显然abab—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;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值