1. 程式人生 > >8.2 kmp 擴展kmp

8.2 kmp 擴展kmp

輸入 問題 說明 公共前綴 oid void 前綴和 標準 led

假設一母串S,子串P

KMP:用於求解子串P在母串S中第一次出現的位置,或是在母串S中出現的次數。(最長公共前綴後綴)

next數組的含義:next[i]表示前面長度為i的子串中,前綴和後綴相等的最大長度。

拓展kmp是對KMP算法的擴展,它解決如下問題:(最長公共前綴)

定義母串S,和子串T,設S的長度為n,T的長度為m,求T與S的每一個後綴的最長公共前綴,也就是說,設extend數組,extend[i]表示T與S[i,n-1]的最長公共前綴,要求出所有extend[i](0<=i<n)。

註意到,如果有一個位置extend[i]=m,則表示T在S中出現,而且是在位置i出現,這就是標準的KMP問題,所以說拓展kmp是對KMP算法的擴展,所以一般將它稱為擴展KMP算法。

next數組的含義:next[i]表示子串T與T[i,m-1](T的第i個後綴)的最長公共前綴(與上面的定義類似,就是以子串代母串)

KMP求最小循環節、循環周期:https://blog.csdn.net/hao_zong_yin/article/details/77455285

定理:假設S的長度為len,則S存在最小循環節,循環節的長度L為len-next[len],子串為S[0…len-next[len]-1]。

(1)如果len可以被len - next[len]整除,則表明字符串S可以完全由循環節循環組成,循環周期T=len/L。

(2)如果不能,說明還需要再添加幾個字母才能補全。需要補的個數是循環個數L-len%L=L-(len-L)%L=L-next[len]%L,L=len-next[len]。

定理可以這麽理解:

對於一個字符串,如abcd abcd abcd,由長度為4的字符串abcd重復3次得到,那麽必然有原字符串的前八位等於後八位。

也就是說,對於某個字符串S,長度為len,由長度為L的字符串s重復R次得到,當R≥2時必然有S[0..len-L-1]=S[L..len-1],字符串下標從0開始

那麽對於KMP算法來說,就有next[len]=len-L。此時L肯定已經是最小的了(因為next的值是前綴和後綴相等的最大長度,即len-L是最大的,那麽在len已經確定的情況下,L是最小的)。

A題:

Description

HHM要給自己的小狗起名字,他是這樣做的,他先找到一個字符串S,字符串S所有前綴與後綴相同的字串,都可以作為名字。現在他想知道所有符合條件的字串的長度。

Input

有多組輸入,每一組輸入包含一個字符串S(1<=length of S <=400000)

Output

輸出所有可能的子串長度,中間以空格結束。最後一個數字後不要有空格。

Sample Input

ababcababababcabab
aaaaa

Sample Output

2 4 9 18
1 2 3 4 5

HINT

此題就是求母串S的公共前綴後綴值(跟子串沒關系)

設len=strlen(S),next[len]等於長度為len的子串(母串S本身)的最長公共前綴後綴值,所以枚舉長度從1到next[len]的前綴後綴,如果相等就記錄長度即可。

技術分享圖片
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
char str[maxn*4];
int Next[400010];
void getnext(char p[])
{
    int plen=strlen(p);
    Next[0]=-1;
    int j=0,k=-1;
    while(j<plen)
    {       //p[k]表示前綴,p[j]表示後綴
        if(k==-1||p[j]==p[k])
        {
            j++;
            k++;
            //if(p[j]==p[k])k=next[k];//因為不能出現p[j]=p[next[j]],所以當出現時需要繼續遞歸,k = next[k] = next[next[k]]
            Next[j]=k;
        }
        else k=Next[k];
    }
}
int main()
{
    while(~scanf("%s",str))
    {
        getnext(str);
        int l=strlen(str);
        int m=Next[l];
        int i,j;
        string a="",b="";
        for(i=0,j=l-1;i<m;i++,j--)
        {
            a=a+str[i];
            b=str[j]+b;
            if(a==b)printf("%d ",i+1);
        }
        printf("%d\n",l);
    }
    return 0;
}
View Code

B題:

Description

HHM碰到了這樣一個問題,給出一個字符串,問它最多由多少相同的子串組成

Input

每個測試數據輸入一個字符串s,s(1<=len(s)<=1000000)。輸入以.結束

Output

輸出最多有多少個相同的子串組成。

Sample Input

abcd
aaaa
ababab
.

Sample Output

1
4
3

HINT

這題就是一個求循環節的題目,相同的子串即為母串的循環節

用len-next[len]求出循環節的長度

用len/(len-next[len])即可得到有幾個相同的子串

技術分享圖片
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
char str[maxn];
int next[maxn];
void getnext(char *s,int next[])
{
    int plen=strlen(s);
    next[0]=-1;
    int j=0,k=-1;
    while(j<=plen-1)
    {       //p[k]表示前綴,p[j]表示後綴
        if(k==-1||s[j]==s[k])
        {
            j++;
            k++;
            //if(p[j]==p[k])k=next[k];//因為不能出現p[j]=p[next[j]],所以當出現時需要繼續遞歸,k = next[k] = next[next[k]]
            next[j]=k;
        }
        else k=next[k];
    }
}
int main()
{
    while(1)
    {
        scanf("%s",str);
        memset(next,0,sizeof next);
        if(str[0]==.)break;
        getnext(str,next);
        int l=strlen(str);
        int ans=1;
        if(l%(l-next[l])==0)//不整除就沒有循環節
        {
            ans=l/(l-next[l]);
        }
        printf("%d\n",ans);
    }
    return 0;
}
View Code

8.2 kmp 擴展kmp