---
# System prepended metadata

title: 【模板】字符串哈希（String Hash）
tags: [競程]

---

【模板】字符串哈希（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)