字符串——KMP模板及習題(持續更新)

kmp模板

//kmp算法的主要作用在於對next數組的運用
//性質1:對於每一個長度len的子串,該子串的最小循環節爲tmp = len-next[len],如果 len%tmp == 0 且 len != tmp , 那麼該串就是以最小循環節長度爲tmp,循環的,如 tmp = 3, len = 12的串“abcabcabcabc”。
//性質2:kmp的next不斷向前遞歸的過程可以保證對於每一個當前前綴,都有一段後綴與之對應

Next[]數組的 Next[i] 記錄的是模式串的0到 i-1 子串的前綴和後綴的相同的最長長度,遞推來求。當 i = 0時就爲-1。
在KMP代碼中,將模式串右移動幾位就是相當於 j 改變一下就行。
如果給定的模式串是:“ABCDABD”,從左至右遍歷整個模式串,其各個子串的前綴後綴分別如下表格所示:1
2
3

//kmp算法的主要作用在於對next數組的運用
//性質1:對於每一個長度len的子串,該子串的最小循環節爲len-next[len]
//性質2:kmp的next不斷向前遞歸的過程可以保證對於每一個當前前綴,都有一段後綴與之對應
const int maxn = 1e6+50;
int next[maxn];
char str[maxn], mo[maxn];//分別爲文本串和模式串
int n1, n2;//兩串的長度

void GetNext()
{
    memset(next, -1, sizeof(next));
    //cin >> mo;
    n2 = strlen(mo);
    int i = 0, j = -1;
    while(i < n2){
        if(j == -1 || mo[i] == mo[j]){
            i++; j++; next[i] = j;
        }
        else{
            j = next[j];
        }
    }
    /*
    for(int i = 0; i < n2; i++){
        cout << next[i] << " ";
    }
    cout << endl;
    */
    return;
}
/*
int KMP()
{
    int i = 0, j = 0;
    n1 = strlen(str);
    while(i < n1){
        if(j == -1 || str[i] == mo[j]){
            i++; j++;
        }
        else{
            j = next[j];
        }
        if(j == n2){
            return i - n2 + 1;
        }
    }
    return -1;
}
*/
int main()
{
    cin >> str;
    cin >> mo;
    GetNext();
    //int cur = KMP();
    return 0;
}

kmp習題

1.hdu1711 Number Sequence

鏈接:hdu1711
題意:給你T組數據,每組包括一個文本串,一個模式串,讓你找模式串第一次在文本串裏面出現的位置,匹配失敗就返回-1
題解:就是一個裸的kmp。
代碼:

const int maxn = 1e6+50;
int T, n, m;
int Next[maxn];
int str[maxn], mo[maxn];

void GetNext(){
    for(int i = 0; i <= m; i++){
        Next[i] = -1;
    }
    int i = 0, j = -1;
    while(i < m)
    {
        if(j == -1 || mo[i] == mo[j]){
            i++; j++; Next[i] = j;
        }
        else{
            j = Next[j];
        }
    }
    return;
}

int KMP()
{
    int i = 0, j = 0;
    while(i < n){
        if(j == -1 || str[i] == mo[j]){
            i++; j++;
        }
        else{
            j = Next[j];
        }
        if(j == m){
            return i - m + 1;
        }
    }
    return -1;
}

int main()
{
    scanf("%d", &T);
    while(T--){
        scanf("%d%d", &n, &m);
        for(int i = 0; i < n; i++)
            scanf("%d", &str[i]);
        for(int i = 0; i < m; i++)
            scanf("%d", &mo[i]);
        GetNext();
        int ans = KMP();
        printf("%d\n", ans);
    }
    return 0;
}

2.hdu1680 Oilipo

鏈接:hdu1680
題意:和上一題差不多,改變的是讓你求模式串在文本串中出現的次數,匹配成功的時候前面用過的可以重複使用。
題解:就是在j == m,的時候,讓j = Next[j] 就行了, 相當於匹配失敗的時候,按規則移動就行。
代碼:

const int maxn = 1e6+50;
int T;
int n, m;
int Next[maxn];
char str[maxn], mo[maxn];

void GetNext()
{
    for(int i = 0; i <= m; i++){
        Next[i] = -1;
    }
    int i = 0, j = -1;
    while(i < m){
        if(j == -1 || mo[i] == mo[j]){
            j++; i++; Next[i] = j;
        }
        else{
            j = Next[j];
        }
    }
    return;
}

int KMP()
{
    int i = 0, j = 0;
    int ans = 0;
    while(i < n){
        if(j == -1 || str[i] == mo[j]){
            i++; j++;
        }
        else{
            j = Next[j];
        }
        if(j == m){
            ans++;
            j = Next[j];//按匹配失敗時的規則就行
        }
    }
    return ans;
}

int main()
{
    scanf("%d", &T);
    while(T--){
        scanf("%s", mo);
        scanf("%s", str);
        n = strlen(str);
        m = strlen(mo);
        GetNext();
        int ans = KMP();
        printf("%d\n", ans);
    }
    return 0;
}

3.hdu2087 剪花布條

鏈接:hdu2087
題意:和上一題差不多,但是匹配成功時,該匹配過的不能再次使用。
題解:匹配成功時,獎 j = 0 就行,相當於從匹配成功的下一個字符串重新從頭開始匹配。
代碼:

#include <bits/stdc++.h>

using namespace std;

const int maxn = 1e4+50;
int n, m;
int Next[maxn];
char str[maxn], mo[maxn];

void GetNext(){
    Next[0] = -1;
    m = strlen(mo);
    int i = 0, j = -1;
    while(i < m){
        if(j == -1 || mo[i] == mo[j]){
            i++; j++; Next[i] = j;
        }
        else{
            j = Next[j];
        }
    }
    return;
}

int KMP(){
    int ans = 0, i = 0, j = 0;
    n = strlen(str);
    while(i < n){
        if(j == -1 || str[i] == mo[j]){
            i++; j++;
        }
        else{
            j = Next[j];
        }
        if(j == m){
            ans++;
            j = 0;
        }
    }
    return ans;
}

int main()
{
    while(true){
        scanf("%s", str);
        if(str[0] == '#')
            break;
        scanf("%s", mo);
        GetNext();
        int ans = KMP();
        printf("%d\n", ans);
    }
    return 0;
}

4.hdu3764 Cycle Nackdace

鏈接:hdu3764
題意:T組,每組給你一個字符串,問你加上最少多長的字符串,可以讓這個字符串變成循環串。
題解:直接求一下Next數組,判斷一下最小循環串就行
代碼:

void GetNext()
{
    Next[0] = -1;
    m = strlen(mo);
    int i = 0, j = -1;
    while(i < m){
        if(j == -1 || mo[i] == mo[j]){
            i++; j++; Next[i] = j;
        }
        else{
            j = Next[j];
        }
    }
    return;
}

int main()
{
    int T;
    scanf("%d", &T);
    while(T--){
        scanf("%s", mo);
        GetNext();
        int tmp = m - Next[m];
        //cout << Next[m] << endl;
        if(m%tmp == 0 && m != tmp){
            printf("0\n");
        }
        else{
            printf("%d\n", tmp-m%tmp);
        }
    }
    return 0;
}

5.hdu1358 Period

鏈接:hdu1358
題意:多組,每組一個字符串,問字符串的所有子串(包括本身)是否爲循環串,若是則輸出該子串的右側的下標(從1開始),以及該子串循環次數是多少。
題解:求Next數組,最小循環tmp = i - Next[i],判斷一下 i % tmp == 0 && tmp != i 就行。
代碼:

void GetNext()
{
    Next[0] = -1;
    int i = 0, j = -1;
    n = strlen(mo);
    while(i < n){
        if(j == -1 || mo[i] == mo[j]){
            i++; j++; Next[i] = j;
        }
        else{
            j = Next[j];
        }
    }
    return ;
}

int main()
{
    int cnt = 1;
    while(scanf("%d", &n)){
        if(n == 0)
            break;
        scanf("%s", mo);
        GetNext();
        printf("Test case #%d\n", cnt++);
        for(int i = 1; i <= n; i++){
            int tmp = i - Next[i];
            if(i % tmp == 0 && tmp != i){
                printf("%d %d\n", i, i / tmp);
            }
        }
        printf("\n");
    }
    return 0;
}

6.poj2406 Power Strings

鏈接:poj2406
題意:多組,每組一個字符串,若該字符串爲循環串則輸出的最大循環次數,否則輸出1。
題解:求Next數組,求解最小循環節就行。
代碼:

void GetNext()
{
    Next[0] = -1;
    n = strlen(mo);
    int i = 0, j = -1;
    while(i < n){
        if(j == -1 || mo[i] == mo[j]){
            i++; j++; Next[i] = j;
        }
        else{
            j = Next[j];
        }
    }
    return ;
}

int main()
{
    while(true){
        scanf("%s", mo);
        if(mo[0] == '.' && strlen(mo) == 1){
            break;
        }
        GetNext();
        int tmp = n - Next[n];
        if(n % tmp == 0 && n != tmp){
            printf("%d\n", n / tmp);
        }
        else{
            printf("1\n");
        }
    }
    return 0;
}

7. poj2752 Seek the Name, Seek the Fame

鏈接:poj2752
題意:給你一個字符串,求出該字符串所有相同的前綴和後綴的長度,按從小到大排序
題解:關鍵是j = Next[j],的 j 就表示比當前相同的前綴和後綴短一些的上一個相同的長度,是對Next數組的運用。
我的收穫:強強強
代碼:

const int maxn = 1e6+50;
int n, m;
int Next[maxn], ans[maxn];;
char str[maxn], mo[maxn];

void GetNext()
{
    Next[0] = -1;
    m = strlen(mo);
    int i = 0, j = -1;
    while(i < m){
        if(j == -1 || mo[i] == mo[j]){
            i++; j++; Next[i] = j;
        }
        else{
            j = Next[j];
        }
    }
    return ;
}

int main()
{
    while(scanf("%s", mo) != EOF){
        //scanf("%s", mo);
        GetNext();
        int cur = 0, j = m;
        while(j != 0){
            ans[cur++] = j;
            j = Next[j];//關鍵
        }
        for(int i = cur-1; i >= 0; i--){
            printf("%d%c", ans[i], i == 0 ? '\n' : ' ');
        }
    }
    return 0;
}

8.hdu2594 Simpsons’ Hidden Talents

鏈接:hdu2594
題意:多組,每組倆個字符串,讓你求出最長前一個字符串的前綴和後一個字符串的後綴相同的字符串以及長度,並輸出。
題解:先將兩個字符串做連接並求出Next,求出min(len1, len2) = Min,若求出來Next[len1+len2] >Min 肯定長了, 就根據Next數組的性質,不斷遞歸找短一點的相同前綴和後綴長度,直到ans <= Min就行。
找的方式是 while(ans > min) { ans = Next[ans]; } 和上一題一樣用到了該性質。

代碼:

void GetNext(){
    Next[0] = -1;
    int i = 0, j = -1;
    while(i < m){
        if(j == -1 || mo[i] == mo[j]){
            i++; j++; Next[i] = j;
        }
        else{
            j = Next[j];
        }
    }
}

int main()
{
    while(scanf("%s%s", a, b) != EOF){
        n1 = strlen(a);
        n2 = strlen(b);
        m = n1 + n2;
        for(int i = 0; i < n1; i++){
            mo[i] = a[i];
        }
        for(int i = n1; i < m; i++){
            mo[i] = b[i-n1];
        }
        mo[m] = '\0';
        GetNext();
        int Min = min(n1, n2);
        int ans = Next[m];
        //cout << endl;
        while(ans > Min){
            //cout << ans << " " << Next[ans] << endl;
            ans = Next[ans];
        }
        for(int i = 0; i < ans; i++){
            printf("%c", mo[i]);
        }
        if(ans == 0){
            printf("0\n");
        }
        else{
            printf(" %d\n", ans);
        }
    }
    return 0;
}

9.hdu3336 Count the string

鏈接:hdu3336
題意:T組,每組給你一個n表示字符串長度,一個字符串,讓你求該字符串的每一個前綴在該字符串中出現的次數,包括串本身。要求答案對10007取模。
題解:就是對該串求一個Next數組,從n開始到1,每次求該長度的字符串的相同的前綴和後綴,ans++,用到第二條性質,while(tmp != 0){ ans++; tmp = Next[tmp];}
代碼:

int main()
{
    scanf("%d", &T);
    while(T--){
        scanf("%d", &m);
        scanf("%s", mo);
        GetNext();
        int ans = 0;
        for(int i = m; i >= 1; i--){
            int cur = i;
            //cout << endl << cur << endl;
            while(cur != 0){
                cur = Next[cur];
                //cout << cur << endl;
                if(cur != 0) ans += 1;
                ans %= 10007;
            }
        }
        ans = (ans+m) % 10007;
        printf("%d\n", ans);
    }
    return 0;
}

10.hdu4300 Clairewd’s message

鏈接:hdu4300
題意:T組,每組倆個字符串,第一個字符串26個字符,每一個位置字符表示 i + ‘a’ 的明文, 轉化爲str[i] 的暗文。第二個字符串表示給你的是一個暗文+一個不全的明文(或者已經全了),暗紋和明文是按第一個字符串表示的轉化規則一一對應的,就是明文是不是全的不知道。現在讓你輸出完整的暗紋+明文。
題解:既然不知到明文全不全,那麼明文的長度一定是小於等於第二個字符串長度m的一半的。那麼先將(m+1)/2長度的暗文轉化爲明文,已有的明文肯定是已經轉化過的暗紋的前綴。此時用Next數組求出該轉化過的字符串的最長前綴和後綴長度。用第二條性質,while(ans > min((m+1)/2, m - (m+1)/2)) ans = Next[ans]; 求出具體的已有的明文長度,然後輸出原來的第二個串,再按照ans輸出剩餘待不全的明文就行。
代碼:這個題一開始我還準備改改求Next[] 的規則,發現好像不太像,後來看了題解才想出來明文長度小於等於m / 2 。

const int maxn = 2e5+50;
int T, n, m;
int Next[maxn];
char str[maxn], mo[maxn];
map<char, char> mm, nn;

void GetNext()
{
    Next[0] = -1;
    int i = 0, j = -1;
    while(i < m){
        if(j == -1 || mo[i] == mo[j]){
            i++; j++; Next[i] = j;
        }
        else{
            j = Next[j];
        }
    }
    return ;
}

int main()
{
    scanf("%d", &T);
    while(T--){
        scanf("%s%s", str, mo);
        n = strlen(str);
        m = strlen(mo);
        for(int i = 0; i < n; i++){
            mm[i+'a'] = str[i];
            nn[str[i]] = char(i+'a');
        }
        for(int i = 0; i < m; i++)
            str[i] = mo[i];//存下來原來的mo串
        int mid = (m+1) / 2;
        for(int i = 0; i < mid; i++){
            mo[i] = nn[mo[i]];
        }
        //cout << mo << endl;
        GetNext();
        int ans = Next[m];
        while(ans > min(mid, m-mid)){
            ans = Next[ans];
        }//第二條性質求明文長度
        //cout << ans << endl;
        for(int i = 0; i < m-ans; i++)
            printf("%c", str[i]);
        for(int i = 0; i < m-ans; i++)
            printf("%c", nn[str[i]]);
        printf("\n");
    }
    return 0;
}

11.hdu3374 String Problem

鏈接:hdu3374
關於最大最小表示法請看博客:放過@字符串的最大最小表示法
題意:多組輸入,每組一個字符串,要求輸出最小表示法的第一個位置和最小表示法出現的次數 以及 最大表示法的第一個位置和出現次數。
題解:按照上述方法求出最大最小表示法的第一個位置,如何判斷出現的次數就要用大KMP的Next數組了,考慮要的到出現的次數,只有當字符串是循環出現的,纔有可能在變換後出現相同的字符串。用Next的第一個性質,求出最小循環節tmp = len-Next[len], 判斷len%tmp 是否爲0,若爲0,則表示是循環字符串,最大最小出現次數就是len/tmp。否則出現次數爲1。
代碼:

const int maxn = 1e6+50;
int m, n;
int Next[maxn];
char str[maxn], mo[maxn];

void GetNext()
{
    m = strlen(mo);
    Next[0] = -1;
    int i = 0, j = -1;
    while(i < m){
        if(j == -1 || mo[i] == mo[j]){
            i++; j++; Next[i] = j;
        }
        else{
            j = Next[j];
        }
    }
    return ;
}

int GetMin(char *s)
{
    n = strlen(s);
    int i = 0, j = 1, k = 0, t;
    while(i < n && j < n && k < n){
        t = s[(i+k)%n] - s[(j+k)%n];
        if(!t) k++;
        else{
            if(t > 0) i += k+1;
            else j += k+1;
            if(i == j) j++;
            k = 0;
        }
    }
    return i < j ? i : j;
}

int GetMax(char *s)
{
    n = strlen(s);
    int i = 0, j = 1, k = 0, t;
    while(i < n && j < n && k < n){
        t = s[(i+k)%n] - s[(j+k)%n];
        if(!t) k++;
        else{
            if(t > 0) j += k+1;
            else i += k+1;
            if(i == j) j++;
            k = 0;
        }
    }
    return i < j ? i : j;
}

int main()
{
    while(scanf("%s", mo) != EOF){
        GetNext();
        int Min = GetMin(mo) + 1;
        int Max = GetMax(mo) + 1;
        int tmp = m - Next[m];
        if(m % tmp == 0 && m != tmp){
            printf("%d %d %d %d\n", Min, m/tmp, Max, m/tmp);
        }
        else{
            printf("%d 1 %d 1\n", Min, Max);
        }
    }
    return 0;
}

12.fzu1901 Period II

鏈接:fzu1901
題意:n組,每組一個字符串,現在讓你求所有長度p使得S[i]=S[i+P] for i in [0…SIZE(S)-p-1]。
題解:用kmp的Next數組第二個性質,一直遞歸求小一號長度相同的前綴和後綴的長度,答案就是用字符串長度m減去每一次遞歸的到的長度tmp,即m-tmp。注意的就是m也是答案之一,因爲空對空也算。
代碼:

const int maxn = 1e6+50;
int n, m;
int Next[maxn];
char mo[maxn];
int ans[maxn];

void GetNext()
{
    Next[0] = -1;
    m = strlen(mo);
    int i = 0, j = -1;
    while(i < m){
        if(j == -1 || mo[i] == mo[j]){
            i++; j++; Next[i] = j;
        }
        else{
            j = Next[j];
        }
    }
    return;
}

int main()
{
    scanf("%d", &n);
    int cur = 1;
    while(n--){
        scanf("%s", mo);
        GetNext();
        int tmp = Next[m];
        int sum = 0;
        while(tmp != 0){
            ans[sum++] = m-tmp;
            tmp = Next[tmp];
        }
        //ans[sum++] = m;
        printf("Case #%d: %d\n", cur++, sum+1);
        for(int i = 0; i < sum; i++){
            printf("%d ", ans[i]);
        }
        printf("%d\n", m);
    }
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章