KMP

前缀函数nxt[i]表示s[0...i]最长相等的真前缀与真后缀的长度。

i-nxt[i]即为s[0...i]的一个周期。

前缀函数求法,j代表的时长度。

下标从0开始。

    for(int i=1,j=0;i<n;i++){/*j最次也是前一位的nxt,从前一位失配的位置开始匹配,后缀与前缀不等时一直往前跳*/
        while(j&&s[i]!=s[j])j=nxt[j-1];/*不相等时一直跳到前一个下标的nxt,因为这里j是长度*/
        if(s[i]==s[j])j++;/*匹配到相等的前缀和后缀,j为下一位*/
        nxt[i]=j;
    }

下标从1开始。

for(int i=2,j=0;i<=n;i++){
    while(j&&s[i]!=s[j+1])j=nxt[j];
    if(s[i]==s[j+1])j++;
    nxt[i]=j;
}

KMP匹配,text为文本串,s为模式串。

下标从1开始。

for(int i=1,j=0;i<=m;i++){
    while(j&&t[i]!=s[j+1])j=nxt[j];
    if(t[i]==s[j+1])j++;
    if(j==n)j=nxt[j];/*匹配完了一整个串,进行相应处理,之后跳回到nxt继续匹配*/
}

下标从0开始。

for(int i=0,j=0;i<m;i++){
    while(j&&t[i]!=s[j])j=nxt[j-1];
    if(t[i]==s[j])j++;
    if(j==n)j=nxt[j];/*跳回继续匹配*/
}

字符串s由a不断自我拼接而成,已知s,求a的最小长度。可以转化题意为求s的最小长度循环,答案为n-nxt[n].

    cin>>n>>s;
    for(int i=1,j=0;i<n;i++){
        while(j&&s[i]!=s[j])j=nxt[j-1];
        if(s[i]==s[j])j++;
        nxt[i]=j;
    }
    cout<<n-nxt[n-1];

求一个字符串s[1...i]内不可重叠公共前后缀数量。首先通过递推求出可重叠公共前后缀,之后再KMP时,当j>i/2就一直跳j的nxt直到小于i/2,此时累加答案。

        for(int i=2,j=0;i<=n;i++){
            while(j&&s[i]!=s[j+1])j=nxt[j];
            if(s[i]==s[j+1])j++;
            nxt[i]=j;
            num[i]=num[j]+1;
        }
        for(int i=1,j=0;i<=n;i++){
            while(j&&s[i]!=s[j+1])j=nxt[j];
            if(s[i]==s[j+1])j++;
            while(j>i>>1)j=nxt[j];
            (ans*=(num[j]+1))%=mod;
        }

求一个字符串所有前缀的最大周期之和。每次KMP时从i开始跳nxt,可以记忆化,找到最小的nxt之后更新ans。

    for(int i=1,j=0;i<=n;i++){
        j=i;
        while(nxt[j])j=nxt[j];
        if(nxt[i])nxt[i]=j;
        ans+=i-j;
    }

失配树,也是border树,多次询问同一个字符串的两个前缀的最长公共border。为nxt数组建立一棵fail树,相当于只有一个字符串的AC自动机的fail树,询问就是两个点的LCA,但是当两个点有祖先和后代关系时,答案是祖先的父亲,因为border不能包括自身。

inline int LCA(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    for(int i=20;~i;i--)if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
    if(x==y)return fa[x][0];
    for(int i=20;~i;i--)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}
    for(int i=2,j=0;i<=n;i++){
        while(j&&s[i]!=s[j+1])j=fa[j][0];
        if(s[i]==s[j+1])j++;
        fa[i][0]=j;
        dep[i]=dep[j]+1;
    }
    for(int j=1;j<=20;j++)for(int i=1;i<=n;i++)fa[i][j]=fa[fa[i][j-1]][j-1];
    int m;
    cin>>m;
    while(m--){
        int x,y;
        cin>>x>>y;
        cout<<LCA(x,y)<<'\n';
    }

给出明文和密文,求密文在明文中可能第一次出现的位置。由于是加密过的,所以只关心距离上一个的单词的距离,第一次出现设为inf,之后进行KMP匹配。但是这样存在问题,文本串中a在整个模式串之前出现过,而模式串却是第一次出现,所以在匹配范围内的a文本串不为inf,模式串为inf,需要判断这种情况,比较文本串中距离上一个数的距离和当前匹配的模式串第几个字符来判断是否还在匹配范围内。

    for(n=1;cin>>s&&s[0]!='$';n++){
        if(!mp[s])a[n]=inf;
        else a[n]=n-mp[s];
        mp[s]=n;
    }
    mp.clear();
    n--;
    for(m=1;cin>>s&&s[0]!='$';m++){
        if(!mp[s])b[m]=inf;
        else b[m]=m-mp[s];
        mp[s]=m;
    }
    m--;
    for(int i=2,j=0;i<=m;i++){
        while(j&&b[i]!=b[j+1])j=nxt[j];
        if(b[i]==b[j+1])j++;
        nxt[i]=j;
    }
    for(int i=1,j=0;i<=n;i++){
        while(j&&(b[j+1]==inf&&a[i]<j+1||b[j+1]!=inf&&a[i]!=b[j+1]))j=nxt[j];
        if(!(b[j+1]==inf&&a[i]<j+1||b[j+1]!=inf&&a[i]!=b[j+1]))j++;
        if(j==m)return cout<<i-m+1,0;
    }

求若干字符串的最长公共子串,字串相同为长度相同且一个串全部加上一个数可以变成另外一个串。首先为每个串做差分,之后枚举第一个串的所有后缀,用这个后缀去和剩下的串进行KMP匹配得到一个公共子串,所有的公共子串取max。

    for(int i=1;i<=n;i++){
        cin>>a[0][i];
        for(int j=0;j<a[0][i];j++)cin>>a[i][j];
        for(int j=0;j<a[0][i];j++)a[i][j]=a[i][j+1]-a[i][j];
        --a[0][i];
    }
    int ans=0;
    for(int i=0;i<a[0][1];i++){
        int k=inf;
        getnext(a[1]+i,a[0][1]-i);
        for(int j=2;j<=n;j++)k=min(KMP(a[j],a[0][j],a[1]+i,a[0][1]-i),k);
        ans=max(ans,k+1);
    }
posted @ 2022-11-14 18:06  半步蒟蒻  阅读(134)  评论(0)    收藏  举报