1. 程式人生 > >P3375 【模板】KMP字符串匹配

P3375 【模板】KMP字符串匹配

般的 證明 一行 多少 badge 描述 數組 包括 sample

題目描述

如題,給出兩個字符串s1和s2,其中s2為s1的子串,求出s2在s1中所有出現的位置。

為了減少騙分的情況,接下來還要輸出子串的前綴數組next。

(如果你不知道這是什麽意思也不要問,去百度搜[kmp算法]學習一下就知道了。)

輸入輸出格式

輸入格式:

第一行為一個字符串,即為s1

第二行為一個字符串,即為s2

輸出格式:

若幹行,每行包含一個整數,表示s2在s1中出現的位置

接下來1行,包括length(s2)個整數,表示前綴數組next[i]的值。

輸入輸出樣例

輸入樣例#1:
ABABABC
ABA
輸出樣例#1:
1
3
0 0 1 

說明

時空限制:1000ms,128M

數據規模

設s1長度為N,s2長度為M

對於30%的數據:N<=15,M<=5

對於70%的數據:N<=10000,M<=100

對於100%的數據:N<=1000000,M<=1000000

樣例說明:

技術分享圖片

所以兩個匹配位置為1和3,輸出1、3

解析:

本來還想用暴力算法去做這道題,結果看到這個玄學的防騙分方式後,果斷選擇花兩個小時學習KMP,順便orz%%%了SRY大佬~~~

KMP基本思想:
比如,在簡單的一次匹配失敗後,我們會想將模式串盡量的右移和主串進行匹配。右移的距離在KMP算法中是如此計算的:在已經匹配的模式串子串中,找出最長的相同的前綴和後綴,然後移動使它們重疊。(摘自百度百科)

之後我們就可以開心的開始敲代碼辣~~~首先我們要考慮的是如何找到最長的相同前綴後綴,最樸素的算法是O(n2)(n為模式串長度(等待匹配的串A為文本串,進行匹配的串B為模式串))

我們來想一想如何優化:首先,在算P[i]之前(p[i]代表模式串前i位字符最長相同前綴後綴),我們已知的是p[0...i-1];我們可以用遞推的方法去求解。

和一般的遞推方法不同的是,這個遞推並沒有固定的遞推式,網上有許多人說是“自己匹配自己”,而我並不這麽認為。。。蒟蒻和大佬的思維果然不一樣,再次orz%%%

舉一個簡單的栗子~:abaa,p[1]=0,p[2]=0,p[3]=1,下面我們重點看一下如何算p[4];由於前邊第一個a和第三個a已經匹配成功,如果2個字符和當前字符可以匹配的話,那麽直接加上1就好了;

重點在於不能匹配的情況,這也是本蒟蒻學習KMP最大的障礙。匹配失敗,證明當前的前綴後綴位數太多了,匹配不上,所以我們要減小前綴的數目,所以我們要在這個前綴裏再找前後綴,看到p[1]=0,所以我們直接看第1個和當前字符能不能匹配即可。發現匹配成功,於是乎p[4]=1;推廣到一般情況:設正在計算第i號字符對應的p[i]值,先去找前i-1個字符的最長相同前後綴(p[i-1]),設p[i-1]=j,然後如果b[j+1]==b[i],則p[i]=j+1;否則順著失配邊走,走到可以匹配或者確定無法匹配位置,對應著下面這段代碼:

for(int i=2;i<=m;i++)
{
    while(j>0&&b[i]!=b[j+1])j=p[j];//好好體會這個while循環,失配的精髓。
    if(b[i]==b[j+1])j++;
    p[i]=j;
}

  

預處理完p數組後,我們就可以開始正式匹配了,結合百度百科的基本思想可以體會到,KMP之所以會省時間,是因為它實際上是一個動態選取最優值的過程,它很好地利用了前綴的性質,沒有無用功。

當不可以匹配的時候,樸素算法是右移一位,而KMP能移多少移多少,也就是所謂的自我匹配。

最後上AC代碼:

#include<iostream>
#include<cstring>
using namespace std;
char a[1000010],b[1000010];
int n,m,p[1000010],j;
int main()
{
    cin>>a+1;
    cin>>b+1;
    n=strlen(a+1);
    m=strlen(b+1);
    for(int i=2;i<=m;i++)
    {
        while(j>0&&b[i]!=b[j+1])j=p[j];
        if(b[i]==b[j+1])j++;
        p[i]=j;
    }
    j=0;
    for(int i=1;i<=n;i++)
    {
        while(j>0&&a[i]!=b[j+1])j=p[j];//失配
        if(a[i]==b[j+1])j++;
        if(j==m)
        {
            cout<<i-m+1<<endl;
            j=p[j];//繼續匹配
        }
    }
    for(int i=1;i<=m;i++)
    {
        cout<<p[i]<<" ";
    }
    return 0;
}

  

P3375 【模板】KMP字符串匹配