KPM演算法求字串的最小週期證明

游辰發表於2024-05-26

先給出公式 ans = n - LPS[n-1]

其中ans為最小週期,n為給出的由假設的週期字串中提取出的子串長度,LPS為字首函式,n-1為字串最後的位置下標

證明如下
證明ans = n - LPS[n-1],思路:
(1) 證明特殊情況,即先對完整週期字串進行證明,這時候的字串組成是 [1][2][3][4] ,即4個週期拼接,所以由字首函式的定義,有

 [1][2][3] = [2][3][4],所以LPS[n-1] = 3*T,即三個週期,則ans = n(即4*T) - LPS[n-1] = 4*T - 3*T = T;

 對於完整週期子串顯然成立.

(2) 證明一般情況,即證明非完整週期字串,假設給出的字串是 [末部分][1][2][3][前部分] ,即中間有完整的週期,兩邊是不確定長度的,可能為0.

(為了方便,此處取[末部分] = [e],[前部分] = [b])

 1,當len([e]) = len([b]) = 0時,即(1)的情況,顯然成立.

 2,當len([e]) = 0,len([b]) != 0時此時字串為 [1][2][3][b],由於[b]是週期的一部分,則[3]中包含[b],有
 [1][2][b] = [2][3][b],此時 LPS[n-1] = 2*T + len([b]), n = 3*T + len([b]),顯然有
 ans = n - LPS[n-1] == 3*T + len([b]) - 2*T - len([b]) = T,成立.
 
 3,當len([b]) = 0,len([e]) != 0時同理.

 4,當len([e]) != 0,且len([b]) != 0時,此時字串為 [e][1][2][3][b],根據2,3,有
 [e][1][2][b] = [e][2][3][b],符合字首函式定義,此時LPS[n-1] = 2*T + len([b+e]),n = 3*T + len([b+e])
 顯然有ans = n - LPS[n-1] = T,得證
 
 5,當[e][b]內部沒有完整的週期時,顯然[e][b]可以自己組成最小週期,此時的LPS[n-1] = 0,ans = len([e+b]),為自己,得證

附上例題加以理解:
1,KMP演算法模板https://www.luogu.com.cn/problem/P3375
2,字串週期模板題https://www.luogu.com.cn/problem/P4391

例題1程式碼解析:

點選檢視程式碼
//背景:剛學LPS函式及其應用KMP,先來道模板題練習,結果發現細節多
//注意:以後我的所有關於KMP的演算法的字串下標均是從0開始(便於與OI-wiki統一,好記憶)
//原理:KMP匹配字串
//時間複雜度:o(n+m)
#include <bits/stdc++.h>
using namespace std;
void Prefixion(int LPS[],string s)//求生成字串的字首函式
{
    LPS[0] = 0;//初始化字首
    for (int i = 1,big = s.size();i<big;i++)//遍歷字串
    {
        int j = LPS[i-1];//獲取上一個位置的LPS
        while(j>0&&s[j] != s[i]) j = LPS[j-1];//尋找第一個使得當前位置i的字首性質仍滿足的j
        if(s[j] == s[i]) j++;//如果是因為相等退出迴圈的,j是位置,那麼長度為j+1
        LPS[i] = j;//記錄當前位置的LPS
    }
}
void KMP()//KMP演算法
{
    string text,pattern;
    cin>>text>>pattern;//輸入文字字串及模板字串(待查詢的字串)
    string cur = pattern + '#' + text;//生成新的字串
    int s1 = pattern.size();//計算模板字串的長度
    int s2 = text.size();//計算文字字串的長度
    int LPS[s2+s1+1];//注意這裡LPS的陣列大小,如果開小了退不出函式(我也不知道為什麼)
    Prefixion(LPS,cur);//求生成字串的字首函式
    vector<int> occurrence;//記錄文字字串匹配成功的起始位置
    for (int i = s1+1;i<=s2+s1;i++)//從生成字串的文字位置開始遍歷
    {
        if(LPS[i] == s1) occurrence.push_back(i-2*s1);//如果滿足當前情況,即記錄下成功匹配的位置(相對文字):當前位置 - 2*模板長度
    }
    for (auto v:occurrence) cout<<v+1<<"\n";//依次輸出位置(相對文字的)
    for (int i = 0;i<s1;i++) cout<<LPS[i]<<" ";//輸出每個字首的函式值
    cout<<"\n";
}
int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t = 1;
    while(t--)
    {
        KMP();
    }
    return 0;
}

例題2程式碼解析:

點選檢視程式碼
//背景:字串的週期性問題,一開始不太理解,後來看了題解自己畫圖推匯出來了
//本題題意:本題給出的是重複拼接字串子串,求最小週期,也就是說假定了原字串是週期函式,給分析證明提供了思路
//公式:ans = n - LPS[n-1]
#include <bits/stdc++.h>
using namespace std;
void Prefixion(int LPS[],string s)//字首函式
{
    LPS[0] = 0;//不要忘了初始化
    for (int i = 1;s[i] != '\0';i++)
    {
        int j = LPS[i-1];
        while(j > 0&&s[j] != s[i]) j = LPS[j-1];
        if(s[j] == s[i]) j++;
        LPS[i] = j;
    }
}
void Solve()
{
    int n;
    string s;
    cin>>n;//輸入子串長度
    cin>>s;//輸入字串
    int LPS[n];//定義字首函式
    Prefixion(LPS,s);//求字首函式
    cout<<n - LPS[n-1]<<"\n";//輸出最小週期
}
int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t = 1;
    while(t--)
    {
        Solve();
    }
    return 0;
}

/*證明ans = n - LPS[n-1],思路:
(1)證明特殊情況,即先對完整週期字串進行證明,這時候的字串組成是 [1][2][3][4] ,即4個週期拼接,所以由字首函式的定義,有

   [1][2][3] = [2][3][4],所以LPS[n-1] = 3*T,即三個週期,則ans = n(即4*T) - LPS[n-1] = 4*T - 3*T = T;
   
   對於完整週期子串顯然成立.
   
(2)證明一般情況,即證明非完整週期字串,假設給出的字串是 [末部分][1][2][3][前部分] ,即中間有完整的週期,兩邊是不確定長度的,可能為0.

   (為了方便,此處取[末部分] = [e],[前部分] = [b])
   
   1,當len([e]) = len([b]) = 0時,即(1)的情況,顯然成立.
   
   2,當len([e]) = 0,len([b]) != 0時此時字串為 [1][2][3][b],由於[b]是週期的一部分,則[3]中包含[b],有
     [1][2][b] = [2][3][b],此時 LPS[n-1] = 2*T + len([b]), n = 3*T + len([b]),顯然有
     ans = n - LPS[n-1] == 3*T + len([b]) - 2*T - len([b]) = T,成立.
     
   3,當len([b]) = 0,len([e]) != 0時同理.
   
   4,當len([e]) != 0,且len([b]) != 0時,此時字串為 [e][1][2][3][b],根據2,3,有
     [e][1][2][b] = [e][2][3][b],符合字首函式定義,此時LPS[n-1] = 2*T + len([b+e]),n = 3*T + len([b+e])
     顯然有ans = n - LPS[n-1] = T,得證
     
   5,當[e][b]內部沒有完整的週期時,顯然[e][b]可以自己組成最小週期,此時的LPS[n-1] = 0,ans = len([e+b]),為自己,得證

碼字不易,多多支援!

相關文章