zoukankan      html  css  js  c++  java
  • 扩展KMP(EXKMP)

    跟Manacher类似,都是利用之前的计算结果来省去不必要的计算

    感觉能搜到的博客写的都是同样的内容,而且并不是很直观...按照自己的理解写一遍

    模板:

    int nxt[N],ext[N];
    
    void getnxt(char *s,int m)
    {
        memset(nxt,0,sizeof(nxt));
        
        nxt[0]=m;
        for(int i=0;i<m-1 && s[i]==s[i+1];i++)
            nxt[1]=i+1;
        
        int l=1;
        for(int i=2;i<m;i++)
        {
            int r=l+nxt[l]-1,j=i+nxt[i-l]-1;
            if(j>=r)
            {
                j=max(0,r-i+1);
                while(i+j<m && s[i+j]==s[j])
                    j++;
                nxt[i]=j;
                l=i;
            }
            else
                nxt[i]=nxt[i-l];
        }
    }
    
    void getext(char *s,int n,char *t,int m)
    {
        memset(ext,0,sizeof(ext));
        getnxt(t,m);
        
        for(int i=0;i<min(n,m) && s[i]==t[i];i++)
            ext[0]=i+1;
        
        int l=0;
        for(int i=1;i<n;i++)
        {
            int r=l+ext[l]-1,j=i+nxt[i-l]-1;
            if(j>=r)
            {
                j=max(0,r-i+1);
                while(i+j<n && j<m && s[i+j]==t[j])
                    j++;
                ext[i]=j;
                l=i;
            }
            else
                ext[i]=nxt[i-l];
        }
    }
    View Code

    ~ KMP与EXKMP ~

    回忆下KMP做了什么事情:

    将待匹配串$S$与模式串$T$进行匹配,$|S|=n,|T|=m$;若$S[i+1] eq T[j+1]$,那么不断使$j=next[j]$直到$S[i+1]$与$T[j+1]$能够匹配上(或者一个都匹配不上)

    在这个过程中,我们能知道$S$与$T$是否能完全匹配

    不过更严格地说,我们能够对于 从$S$每一个位置为结束的后缀 找到其在$T$中的最大匹配长度,即有

    [S[i-len+1 ... i]=T[0 ... len-1], iin [0,n-1]]

    而EXKMP要做的事情跟KMP很类似,不过求的是 从$S$每一个位置开始的前缀 与$T$的最大匹配长度,即

    [S[i ... i+len-1]=T[0 ... len-1], iin [0,n-1]]


    ~ 求nxt(next)数组 ~

    我们先不考虑$S$数组,而是求$T$的每一个前缀与$T$的最大匹配长度$nxt[i]$,即有

    [T[i ... i+nxt[i]-1]=T[0 ... nxt[i]-1], iin [0,m-1]]

    假设$nxt[0 ... i-1]$的结果已经计算完毕,现在将要计算$nxt[i]$

    (显然$nxt[0]=m$;这是一个特例,不作为计算$nxt[1 ... m-1]$的参考)

    那么存在一个前缀起点$lin [0,i-1]$,能够使得$l+nxt[l]-1$最大,将其记为$r=l+nxt[l]-1$;这代表了 以$T[1 ... i-1]$为起点的前缀 最多匹配到的位置,那么有

    [T[l ... r]=T[0 ... r-l]]

    考虑利用$T[l ... r]$来简化计算;将这个子串的起点从$l$变为$i$,由于$l ... r$均已匹配完毕,那么有

    [T[i ... r]=T[i-l ... r-l]]

    也就是说,以$i$开头的一段前缀与$i-l$开头的一段前缀相同;那么$nxt[i]$可以参考$nxt[i-l]$进行计算,但需要分情况讨论:

    1. $i+nxt[i-l]-1<r$

    左右两边同减$l$,得到$i-l+nxt[i-l]-1<r-l$,也就是说$T[i-l ... r-l]$仅能与$T[0 ... nxt[i-l]-1]$完全匹配,之后的就匹配不上了

    那么也就说明$T[i ... r]$最多能匹配到$T[0 ... nxt[i-l]-1]$,即$nxt[i]=nxt[i-l]$

    2. $i+nxt[i-l]-1geq r$

    仍然同减$l$,得到$i-l+nxt[i-l]-1geq r-l$,也就是说$T[i-l ... r-l]$至少能与$T[0 ... r-i]$完全匹配

    也许以$i-l$开头的前缀能够与$T$匹配上更大的长度,但是这对于计算$nxt[i]$没有意义,因为我们仅有$T[i ... r]=T[i-l ... r-l]$,而$T[r]$之后的字符串我们并不知道

    于是从$r+1$开始,暴力将$T[r+j]$与$T[r-i+j]$尝试进行匹配($jgeq 1, r+j<m$);直到$T[r+j'] eq T[r-i+j']$,则可以得到$nxt[i]=r-i+j'$

    分析一下复杂度:

    情况1显然$O(1)$就完事了;而情况2中的暴力匹配会使得$i+nxt[i]-1>r$,也就是将下一次计算的$r$不断右移,最多移动$m$次

    于是整体为$O(m)$

    在具体实现的时候,我们需要暴力计算一下$nxt[1]$以供之后的计算参考

    void getnxt(char *s,int m)
    {
        nxt[0]=m;
        for(int i=0;i<m-1 && s[i]==s[i+1];i++)
            nxt[1]=i+1;
        
        int l=1;
        for(int i=2;i<m;i++)
        {
            int r=l+nxt[l]-1,j=i+nxt[i-l]-1;
            if(j>=r)
            {
                j=max(0,r-i+1);
                while(i+j<m && s[i+j]==s[j])
                    j++;
                nxt[i]=j;
                l=i;
            }
            else
                nxt[i]=nxt[i-l];
        }
    }

    ~ 求ext(extend)数组 ~

    上面所计算的$nxt$数组是为了计算$ext$数组服务的

    而$ext$的计算跟$nxt$的计算方法几乎相同

    假设$ext[0 ... i-1]$已经计算完毕,且有$lin [0,i-1]$使得$r=l+ext[l]-1$最大,那么有

    [S[l ... r]=T[0 ... r-l]]

    将$S[l ... r]$的左端点移动到$i$,得到

    [S[i ... r]=T[i-l ... r-l]]

    于是$ext[i]$可以参考$nxt[i-l]$进行计算,注意这里是$nxt[i-l]$而不是$ext[i-l]$(因为是与$T[i-l ... r-l]$相同,而不是$S[i-l ... r-l]$)

    之后的讨论略去了,是一样的

    复杂度也是类似的与$r$的移动次数有关,为$O(n)$;于是总复杂度为$O(n+m)$

    具体实现时需要暴力计算$ext[0]$

    void getext(char *s,int n,char *t,int m)
    {
        getnxt(t,m);
        
        for(int i=0;i<min(n,m) && s[i]==t[i];i++)
            ext[0]=i+1;
        
        int l=0;
        for(int i=1;i<n;i++)
        {
            int r=l+ext[l]-1,j=i+nxt[i-l]-1;
            if(j>=r)
            {
                j=max(0,r-i+1);
                while(i+j<n && j<m && s[i+j]==t[j])
                    j++;
                ext[i]=j;
                l=i;
            }
            else
                ext[i]=nxt[i-l];
        }
    }

    ~ 一些题目 ~

    Luogu P5410  (【模板】扩展 KMP(Z 函数))

    $z$是对$nxt$求的,$p$是对$ext$求的;题目有点卡常

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    const int N=20000005;
    
    int nxt[N],ext[N];
    
    void getnxt(char *s,int m)
    {
        nxt[0]=m;
        for(int i=0;i<m-1 && s[i]==s[i+1];i++)
            nxt[1]=i+1;
        
        int l=1;
        for(int i=2;i<m;i++)
        {
            int r=l+nxt[l]-1,j=i+nxt[i-l]-1;
            if(j>=r)
            {
                j=max(0,r-i+1);
                while(i+j<m && s[i+j]==s[j])
                    j++;
                nxt[i]=j;
                l=i;
            }
            else
                nxt[i]=nxt[i-l];
        }
    }
    
    void getext(char *s,int n,char *t,int m)
    {
        getnxt(t,m);
        
        for(int i=0;i<min(n,m) && s[i]==t[i];i++)
            ext[0]=i+1;
        
        int l=0;
        for(int i=1;i<n;i++)
        {
            int r=l+ext[l]-1,j=i+nxt[i-l]-1;
            if(j>=r)
            {
                j=max(0,r-i+1);
                while(i+j<n && j<m && s[i+j]==t[j])
                    j++;
                ext[i]=j;
                l=i;
            }
            else
                ext[i]=nxt[i-l];
        }
    }
    
    int n,m;
    char s[N],t[N];
    
    void read(char *S,int &len)
    {
        char ch=getchar();
        while(ch<'a' || ch>'z')
            ch=getchar();
        while(ch>='a' && ch<='z')
            S[len++]=ch,ch=getchar();
    }
    
    int main()
    {
        read(s,n),read(t,m);
        
        getext(s,n,t,m);
        
        long long ans;
        ans=0;
        for(int i=0;i<m;i++)
            ans^=1LL*(i+1)*(nxt[i]+1);
        printf("%lld
    ",ans);
        ans=0;
        for(int i=0;i<n;i++)
            ans^=1LL*(i+1)*(ext[i]+1);
        printf("%lld
    ",ans);
        
        return 0;
    }
    View Code

    HDU 4333  ($Revolving Digits$)

    考虑将字符串s复制一倍

    那么若$nxt[i]geq n$就说明产生循环(因为在这题中,出现重复数字仅可能出现在操作了循环节次时),由于题目要求数字互不相同可以直接break

    否则比较$s[i+nxt[i]]$和$s[nxt[i]]$的大小即可

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    const int N=200005;
    
    int nxt[N],ext[N];
    
    void getnxt(char *s,int m)
    {
        memset(nxt,0,sizeof(nxt));
        
        nxt[0]=m;
        for(int i=0;i<m-1 && s[i]==s[i+1];i++)
            nxt[1]=i+1;
        
        int l=1;
        for(int i=2;i<m;i++)
        {
            int r=l+nxt[l]-1,j=i+nxt[i-l]-1;
            if(j>=r)
            {
                j=max(0,r-i+1);
                while(i+j<m && s[i+j]==s[j])
                    j++;
                nxt[i]=j;
                l=i;
            }
            else
                nxt[i]=nxt[i-l];
        }
    }
    
    void getext(char *s,int n,char *t,int m)
    {
        memset(ext,0,sizeof(ext));
        getnxt(t,m);
        
        for(int i=0;i<min(n,m) && s[i]==t[i];i++)
            ext[0]=i+1;
        
        int l=0;
        for(int i=1;i<n;i++)
        {
            int r=l+ext[l]-1,j=i+nxt[i-l]-1;
            if(j>=r)
            {
                j=max(0,r-i+1);
                while(i+j<n && j<m && s[i+j]==t[j])
                    j++;
                ext[i]=j;
                l=i;
            }
            else
                ext[i]=nxt[i-l];
        }
    }
    
    int n;
    char s[N];
    
    int main()
    {
        int T;
        scanf("%d",&T);
        for(int kase=1;kase<=T;kase++)
        {
            scanf("%s",s);
            n=strlen(s);
            
            for(int i=n;i<n*2;i++)
                s[i]=s[i-n];
            n*=2;
            
            getnxt(s,n);
            
            int L=0,E=1,G=0;
            for(int i=1;i<n/2;i++)
            {
                if(nxt[i]>=n/2)
                    break;
                else
                    if(s[i+nxt[i]]<s[nxt[i]])
                        L++;
                    else
                        G++;
            }
            printf("Case %d: %d %d %d
    ",kase,L,E,G);
        }
        return 0;
    }
    View Code

    HDU 3613  ($Best Reward$)

    这是一道条件比较特殊的回文题目,用EXKMP跟Manacher差不多方便

    由于题目要将一个字符串$s$切成两段,那么不妨看做左半边、右半边两个部分

    将$s$左右翻转的串称为$rs$

    对于右半边,如果它是回文串,那么相当于一个$s$的后缀能与$rs$匹配上,即$s[i...n-1]=rs[0...n-i-1]$

    对于左半边,如果它是回文串,那么相反地,相当于一个$rs$的后缀能与$s$匹配上,即$rs[i...n-1]=s[0...n-i-1]$

    每一半产生的贡献用前缀和处理一下就好了;最后扫一遍将两半拼起来、贡献相加

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    const int N=500005;
    
    int nxt[N],ext[N];
    
    void getnxt(char *s,int m)
    {
        memset(nxt,0,sizeof(nxt));
        
        nxt[0]=m;
        for(int i=0;i<m-1 && s[i]==s[i+1];i++)
            nxt[1]=i+1;
        
        int l=1;
        for(int i=2;i<m;i++)
        {
            int r=l+nxt[l]-1,j=i+nxt[i-l]-1;
            if(j>=r)
            {
                j=max(0,r-i+1);
                while(i+j<m && s[i+j]==s[j])
                    j++;
                nxt[i]=j;
                l=i;
            }
            else
                nxt[i]=nxt[i-l];
        }
    }
    
    void getext(char *s,int n,char *t,int m)
    {
        memset(ext,0,sizeof(ext));
        getnxt(t,m);
        
        for(int i=0;i<min(n,m) && s[i]==t[i];i++)
            ext[0]=i+1;
        
        int l=0;
        for(int i=1;i<n;i++)
        {
            int r=l+ext[l]-1,j=i+nxt[i-l]-1;
            if(j>=r)
            {
                j=max(0,r-i+1);
                while(i+j<n && j<m && s[i+j]==t[j])
                    j++;
                ext[i]=j;
                l=i;
            }
            else
                ext[i]=nxt[i-l];
        }
    }
    
    int n;
    char s[N],rs[N];
    int val[30];
    
    int pre[N];
    
    inline int sum(int l,int r)
    {
        return pre[r+1]-pre[l];
    }
    
    int L[N],R[N];
    
    int main()
    {
        int T;
        scanf("%d",&T);
        while(T--)
        {
            memset(L,0,sizeof(L));
            memset(R,0,sizeof(R));
            
            for(int i=0;i<26;i++)
                scanf("%d",&val[i]);
            
            scanf("%s",s);
            n=strlen(s);
            
            for(int i=0;i<n;i++)
            {
                rs[n-i-1]=s[i];
                pre[i+1]=pre[i]+val[s[i]-'a'];
            }
            
            getext(s,n,rs,n);
            
            for(int i=1;i<n;i++)
                if(i+ext[i]==n)
                    R[i]=sum(i,n-1);
            
            getext(rs,n,s,n);
            
            for(int i=1;i<n;i++)
                if(i+ext[i]==n)
                    L[n-1-i]=sum(0,n-1-i);
            
            int ans=-(1<<30);
            for(int i=0;i<n-1;i++)
                ans=max(ans,L[i]+R[i+1]);
            printf("%d
    ",ans);
        }
        return 0;
    }
    View Code

    计蒜客A2150  ($Mediocre String Problem$,$2018ICPC$南京)

    第一次现场赛的题...心酸的回忆

    题目要求找出$i,j,k$使得$s[i...j]+t[1...k]$回文;而其中特意规定了$j-i+1>k$,即有超过一半的长度在$s$中

    那么显然$s[i...i+k-1]$与$t[1...k]$对称,而$s[i+k...j]$是回文

    用EXKMP,我们可以求出$s$的以每个位置为结尾的后缀 与$t$的前缀 的公共对称长度$len$,即$s[i-len[i]+1...i]$与$t[1...len[i]]$对称;这可以转化成$s$的翻转串$rs$与$t$的公共前缀长度,即$rs[i...i+len[i]-1]=t[1...len[i]]$,用EXKMP跑一边就有了

    之后就是计算以$s$的每一个位置为起点的回文串长度了;用Manacher跑一边后,对于以每个位置为中心的回文串进行差分(以该回文串前半段中任意一处作为起点都是可以的)得出所需的数组$par$

    最后循环一遍,枚举$s$中对称部分的最右端$i$(即按照原题目意思中的$i+k-1$),那么每处的贡献就是$len[i] imes par[i+1]$

    所以这题其实就是一个缝合怪,分析清楚了就不难

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    typedef long long ll;
    const int N=1000005;
    
    int p[2*N];
    
    //s,n均为插入过#的  返回最长回文串长度 
    int manacher(char *s,int n)
    {
        int mx=0,id=0,res=0;
        for(int i=1;i<=n;i++)
        {
            p[i]=(mx>i?min(p[2*id-i],mx-i):1);
            while(i-p[i]>=1 && i+p[i]<=n && s[i-p[i]]==s[i+p[i]])
                p[i]++;
            
            res=max(res,p[i]-1);
            if(i+p[i]>mx)
                mx=i+p[i],id=i;
        }
        return res;
    }
    
    int nxt[N],ext[N];
    
    void getnxt(char *s,int m)
    {
        memset(nxt,0,sizeof(nxt));
        
        nxt[0]=m;
        for(int i=0;i<m-1 && s[i]==s[i+1];i++)
            nxt[1]=i+1;
        
        int l=1;
        for(int i=2;i<m;i++)
        {
            int r=l+nxt[l]-1,j=i+nxt[i-l]-1;
            if(j>=r)
            {
                j=max(0,r-i+1);
                while(i+j<m && s[i+j]==s[j])
                    j++;
                nxt[i]=j;
                l=i;
            }
            else
                nxt[i]=nxt[i-l];
        }
    }
    
    void getext(char *s,int n,char *t,int m)
    {
        memset(ext,0,sizeof(ext));
        getnxt(t,m);
        
        for(int i=0;i<min(n,m) && s[i]==t[i];i++)
            ext[0]=i+1;
        
        int l=0;
        for(int i=1;i<n;i++)
        {
            int r=l+ext[l]-1,j=i+nxt[i-l]-1;
            if(j>=r)
            {
                j=max(0,r-i+1);
                while(i+j<n && j<m && s[i+j]==t[j])
                    j++;
                ext[i]=j;
                l=i;
            }
            else
                ext[i]=nxt[i-l];
        }
    }
    
    int n,m;
    char s[N],t[N];
    char ns[2*N],rs[N];
    
    int par[N];
    
    int main()
    {
        scanf("%s",s);
        scanf("%s",t);
        n=strlen(s),m=strlen(t);
        
        for(int i=0;i<n;i++)
            rs[n-i-1]=s[i];
        
        ns[1]='#';
        for(int i=0;i<n;i++)
            ns[i*2+2]=s[i],ns[i*2+3]='#';
        
        manacher(ns,n*2+1);
        
        for(int i=1;i<=2*n+1;i++)
            if(i&1)
            {
                if(p[i]==1)
                    continue;
                par[i/2-(p[i]-1)/2]++;
                par[i/2]--;
            }
            else
            {
                par[i/2-(p[i]-1)/2-1]++;
                par[i/2]--;
            }
        
        for(int i=1;i<n;i++)
            par[i]=par[i-1]+par[i];
        
        getext(rs,n,t,m);
        
        ll ans=0;
        for(int i=1;i<n;i++)
            ans+=1LL*ext[i]*par[n-i];
        printf("%lld
    ",ans);
        return 0;
    }
    View Code

    Nowcoder 1099C  ($Distinct Substring$)

    在字符串$s$的后面添加一个字符$c$后,会多产生$n+1$个以$c$为结尾的子串,那么就只需要考虑这些子串对于$h(c)$的贡献

    考虑枚举题目中的$c$

    那么剩下来的问题就是求最小的$i$使得$s[i...n]+c=s[L,R]$、且$s[R]=c$

    这是因为,若$i'geq i$,那么有$s[i'...n]+c=s[L+i'-i...R]$,即已被计入过了$f(s_1,...,s_n)$,不会对$h(c)$产生贡献;若$i'<i$,那么有$s[i'...n]+c eq s[L+i'-i...R]$,会对$h(c)$产生$1$的贡献

    要求$s[i...n]+c=s[L...R]$的最小$i$,可以将$s$串反转为$rs$,那么就变成了$c+rs[1...n-i+1]=rs[n-R+1...n-L+1]$;若不看$c$,就相当于$rs[1...n-i+1]=rs[n-R+2...n-L+1]$会对$h(rs[n-R+1])$产生贡献,显然用exkmp可以解决

    最终,有$h(rs[n-R+1])=n+1-nxt[n-R+2]$

    需要注意,若$c$已出现在$s$中过,那么需要对$h(c)$再减去$1$

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    typedef long long ll;
    const int N=1000005;
    const int MOD=1000000007;
    
    int nxt[N];
    
    void getnxt(int *s,int m)
    {
        for(int i=0;i<m;i++)
            nxt[i]=0;
        
        nxt[0]=m;
        for(int i=0;i<m-1 && s[i]==s[i+1];i++)
            nxt[1]=i+1;
        
        int l=1;
        for(int i=2;i<m;i++)
        {
            int r=l+nxt[l]-1,j=i+nxt[i-l]-1;
            if(j>=r)
            {
                j=max(0,r-i+1);
                while(i+j<m && s[i+j]==s[j])
                    j++;
                nxt[i]=j;
                l=i;
            }
            else
                nxt[i]=nxt[i-l];
        }
    }
    
    int n,m;
    int s[N];
    
    int vis[N],val[N];
    
    int main()
    {
        while(scanf("%d%d",&n,&m)!=EOF)
        {
            for(int i=0;i<=max(n,m);i++)
                vis[i]=val[i]=0;
            
            for(int i=1;i<=n;i++)
                scanf("%d",&s[n-i]),vis[s[n-i]]=1;
            
            getnxt(s,n);
            
            for(int i=1;i<n;i++)
                val[s[i-1]]=max(val[s[i-1]],nxt[i]);
            
            ll ans=0,pw=3;
            for(int i=1;i<=m;i++)
            {
                ans^=(n+1-val[i]-vis[i])*pw%MOD;
                pw=pw*3%MOD;
            }
            printf("%lld
    ",ans);
        }
        return 0;
    }
    View Code

    暂时先补到这里,遇到题目再放上来

    (完)

  • 相关阅读:
    C语言基于单链表得学生成绩管理系统
    C语言实现扫雷小程序外挂,棒棒的
    小白学习C语言一定要掌握的那些知识点!
    C语言快速入门教程之10分钟快速掌握数据类型
    神奇的C语言,这才是C语言大牛操作,作为面试题,怕是秒杀众人
    多线程
    java基础- Collection和map
    String 和 new String
    idea快捷键
    用bootstrap 分页插件问题
  • 原文地址:https://www.cnblogs.com/LiuRunky/p/EXKMP.html
Copyright © 2011-2022 走看看