【模板】字符串哈希(String Hash) === 字符串的比較很慢,因此我們把一個字符串映射到整數。那麼我們可以很方便的比較字串是否相等。 Hash 的核心思想是把輸入映射到一個值域比較小的範圍,方便我們做比較。因為哈希函數是一個多對少的函數映射,因此: - 如果兩輸入的哈希值不同,那他們一定不同 - 如果兩輸入的哈希值相同,那他們可能相同(也可能不同,我們稱為碰撞。我們希望碰撞的概率很小) 我們通常這樣定義 Hash 函數,用於字符串哈希: $$ \begin{align} h(s) &= s[0] b^{n-1} + s[1]b^{n-2} + \dots + s[n-1]b^0 &\mod M \\ &= \sum_{i=0}^{n-1} s[i]\times b^{n-i-1} &\mod M \end{align} $$ 我們可以想像成把字符串 $s$ 轉成一個 $b$ 進位的係數,每一位的值是 $s[i]$。而字串開頭在高位,字串結尾在低位。最後的結果需要模一個質數。 我們通常選擇 $M$ 為一個盡量大的質數,$b$ 為一個比最大字符大的質數。 ```cpp const int M = 1e9 + 7; const int B = 233; typedef long long ll; int get_hash(const string& s) { int res = 0; for (int i = 0; i < s.size(); ++i) { res = ((ll)res * B + s[i]) % M; } return res; } bool cmp(const string& s, const string& t) { return get_hash(s) == get_hash(t); } ``` ## 子串哈希 單次計算一個字符串的哈希值,$O(n)$ 的時間,我們不如直接暴力匹配。但哈希的好處是,我們可以快速取的一個子串的哈希值。 我們採取前綴和的思想,首先對每個前綴預處理出哈希值,再想辦法從兩個前綴湊出區間答案。我們令前綴的哈希值: $H(i) = h(s[0:i)\,)$ 怎麼快速計算出 $H(i)$ 呢? $$ \begin{align} H(i-1) &= s[0]b^{i-2} + s[1]b^{i-3} + \dots + s[i-2]b^0 &\mod M \\ H(i) &= s[0]b^{i-1} + s[1]b^{i-2} + \dots + s[i-2 ]b^1 + s[i-1]b^0 &\mod M \end{align} $$ 可以觀察到: $$ H(i) = H(i-1) \times b + s[i-1] \mod M $$ 因此,我們可以 $O(n)$ 計算出每個前綴的哈希值。 現在,我們想要求出: $$ h(s[l:r)) = s[l]b^{r-l-1}+ \dots + s[r-1]b^0 \mod M $$ 我們可以拿 $H(r)$ 扣掉前面多餘的部分: $s[0]^{r-1}+ \dots + s[l-1]b^{r-l}$。 這一段字符串的資訊在 $H(l)$ 裡,差了 $b^{r-l}$ 倍,只要乘回去就好了。因此我們可以得出: $$ h(s[l:r)) = H(r) - H(l) \times b^{r-l} \mod M $$ ## 練習 [【模板】manacher 算法 - 洛谷](https://www.luogu.com.cn/problem/P3805)