[字符串入門]Z-函數(擴展 KMP)


一個小約定:下文中的所有字符串下標都從 \(0\) 開始。

#1.0 什么是 Z-函數

對於一個長度為 \(n\) 的字符串 \(S\),定義函數 \(z(i)\) 表示 \(S[i,n-1]\),即以 \(S[i]\) 開頭的后綴,與 \(S\) 的最長相同前綴(\(\texttt{Longest Common Prefix, LCP}\))的長度,特別地,我們定義 \(z(0)=0\)\(z\) 被稱為 \(S\)Z-函數

別看它還有另一個名字擴展 KMP,但是實際上 \(\texttt{KMP}\) 算法與 \(\tt{Z}\)-函數除了看起來思想上很像,\(\tt{Z}\)-函數比 \(\tt{KMP}\) 能實現的功能好像多一點外,沒有任何聯系。

#2.0 求解 Z-函數

#2.1 朴素算法

\[\begin{aligned} &\text{Procedure}\ Brute\_Force(s:string)\\ 1.\ &\quad len\gets length(s)\\ 2.\ &\quad\text{Set all elements in }z\text{ to }0\\ 3.\ &\quad\textbf{For }i\text{ to }len-1\\ 4.\ &\quad\qquad\textbf{While }i+z[i]<n\text{ and }s[z[i]]=s[i+z[i]]\\ 5.\ &\quad\qquad\qquad z[i]\gets z[i]+1\\ \end{aligned} \]

很顯然,上面的做法是 \(O(n^2)\) 的。

#2.2 線性算法

這里有一個思想與 \(\tt{KMP}\) 有些類似:運用之前已有的狀態加速計算當前狀態。這種思想在 \(\tt{Manacher}\) 算法等許多字符串算法中同樣有體現。

我們假設當前已經計算出了 \(z(0),z(1),\cdots,z(i-1)\) ,現在來考慮如何計算 \(z(i).\)

先來定義幾個概念:

  • 匹配段(Z-Box):對於 \(x\),我們稱 \([x,x+z(x)-1]\)\(x\) 的匹配段;
  • 記當前右端點最靠右的匹配段為 \([l,r].\)

在計算 \(z(i)\) 過程中,保證 \(l\leq i\)。初始時 \(l=r=0\)

如果當前 \(i\leq r\),那么根據 \(z\) 的定義有 \(S[l,r]=S[0,r-l]\),所以有 \(S[i,r]=S[i-l,r-l]\),那么 \(S[i,n-1]\)\(S\)\(\tt{LCP}\) 長度 \(z(i)\) 只有以下可能:

  1. \(z(i-l)<r-i+1\) 時,\(z(i)=z(i-l)\)

    來看下面這張圖,我們知道根據 \(z\) 的定義,應當有 \(S[i-l,i-l+z(i-l)-1]=S[0,z[i-l]-1]\),而又有 \(S[i,r]=S[i-l,r-l]\),所以如果 \(z(i-l)<r-i+1\),意味着相同前綴的長度不可能超過 \(z(i-l)\),否則與 \(z(i-l)\) 的定義相悖。

  1. \(z(i-l)\geq r-i+1\) 時,應當先令 \(z(i)=r-i+1\),再盡可能地向后擴展。

    如同下面這張圖,我們只能確定 \(S[i,r]=S[i-l,r-l]\) 相同,后面的無法確定。

\(i>r\) 時,我們無法用已知狀態進行轉移,只能暴力向后擴展。

結束當前計算后,我們檢查是否有 \(i+z(i)-1>r\),如果是,那么更新 \([l,r].\)

於是得到代碼:

inline void z_func() {
    for (int i = 1, l = 0, r = 0; i < lenb; ++ i) {
        if (i <= r && z[i - l] < r - i + 1)
          z[i] = z[i - l];
        else {
            /*注意進入 else 的可能時 r < i 的情況,
            所以下面的 z[i] 應當取 Max(0, r - i + 1)*/
            z[i] = Max(0, r - i + 1);
            while (i + z[i] < lenb && b[z[i]] == b[i + z[i]])
              ++ z[i]; //盡可能向后擴展。
        }
        if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
        /*更新 [l,r] 的范圍*/
    }
}

#3.0 Z-函數的應用

#3.1 LG P5410 擴展 KMP(Z 函數)

並不知道該給這種應用起什么名字。

  • 操作一就是基礎的 \(\tt{Z}\)-函數,只不過要注意需要單獨處理 \(z(0)\),顯然是 \(b\) 的長度。

  • 操作二與 \(\tt Z\)-函數的定義十分相似,所以依舊考慮使用已經求出的 \(z\) 進行加速求解。

整體的討論與上面沒有任何區別,這里略去不寫。注意仍需單獨處理。

const int N = 20000100;
const int INF = 0x3fffffff;

int lena, lenb, z[N], p[N];
ll ans1, ans2;
string a, b;

template <typename T>
inline T Max(const T a, const T b) {
    return a > b ? a : b;
}

template <typename T>
inline T Min(const T a, const T b) {
    return a < b ? a : b;
}

inline void z_func() {
    for (int i = 1, l = 0, r = 0; i < lenb; ++ i) {
        if (i <= r && z[i - l] < r - i + 1)
          z[i] = z[i - l];
        else {
            z[i] = Max(0, r - i + 1);
            while (i + z[i] < lenb && b[z[i]] == b[i + z[i]])
              ++ z[i];
        }
        if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
    }
}

inline void p_func() {
    for (int i = 1, l = 0, r = 0; i < lena; ++ i) {
        if (i <= r && z[i - l] < r - i + 1)
          p[i] = z[i - l];
        else {
            p[i] = Max(0, r - i + 1);
            while (i + p[i] < lena && b[p[i]] == a[i + p[i]])
              ++ p[i];
        }
        if (i + p[i] - 1 > r) l = i, r = i + p[i] - 1;
    }
    while (p[0] < Min(lena, lenb) && b[p[0]] == a[p[0]]) p[0] ++;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cin >> a >> b;
    lena = a.length(), lenb = b.length();
    z_func(); z[0] = lenb; p_func();
    for (int i = 0; i < lenb; i ++)
      ans1 ^= ((ll)(i + 1) * (z[i] + 1));
    for (int i = 0; i < lena; i ++)
      ans2 ^= ((ll)(i + 1) * (p[i] + 1));
    printf("%lld\n%lld", ans1, ans2);
    return 0;
}

#3.2 模式串匹配

\(\tt Z\) 函數做模式串匹配很簡單,將要尋找的串憑借在文本串前,兩者中間用 # 等不會在兩個串中出現的字符連接,求出新串的 \(\tt Z\) 函數,枚舉每個位置上的 \(z\),如果 \(z[i]\) 等於模式串的長度,那么該位置存在我們要找的模式串。

中間的 # 是為了保證匹配的最大長度不會超過模式串的長度。

正確性顯然。

參考資料

[1] Z 函數(擴展 KMP) - OI Wiki

[2] Z函數(擴展KMP)&前綴函數的總結~ - NuoCarter


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM