using std::string;constexpr int M = 1e9 + 7;constexpr int B = 233;using ll = long long;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);}
Python
M = int(1e9 + 7)B = 233def get_hash(s): res = 0 for char in s: res = (res * B + ord(char)) % M return resdef cmp(s, t): return get_hash(s) == get_hash(t)
双值 Hash:
[list2tab]
C++
using ull = unsigned long long;ull base = 131;ull mod1 = 212370440130137957, mod2 = 1e9 + 7;ull get_hash1(std::string s) { int len = s.size(); ull ans = 0; for (int i = 0; i < len; i++) ans = (ans * base + (ull)s[i]) % mod1; return ans;}ull get_hash2(std::string s) { int len = s.size(); ull ans = 0; for (int i = 0; i < len; i++) ans = (ans * base + (ull)s[i]) % mod2; return ans;}bool cmp(const std::string s, const std::string t) { bool f1 = get_hash1(s) != get_hash1(t); bool f2 = get_hash2(s) != get_hash2(t); return f1 || f2;}
Python
def get_hash1(s: str) -> int: base = 131 mod1 = 212370440130137957 ans = 0 for char in s: ans = (ans * base + ord(char)) % mod1 return ansdef get_hash2(s: str) -> int: base = 131 mod2 = 1000000007 ans = 0 for char in s: ans = (ans * base + ord(char)) % mod2 return ansdef cmp(s: str, t: str) -> bool: f1 = get_hash1(s) != get_hash1(t) f2 = get_hash2(s) != get_hash2(t) return f1 or f2
Hash 的应用
字符串匹配
求出模式串的哈希值后,求出文本串每个长度为模式串长度的子串的哈希值,分别与模式串的哈希值比较即可。
允许 k 次失配的字符串匹配
问题:给定长为 n 的源串 s,以及长度为 m 的模式串 p,要求查找源串中有多少子串与模式串匹配。s′ 与 s 匹配,当且仅当 s′ 与 s 长度相同,且最多有 k 个位置字符不同。其中 1≤n,m≤106,0≤k≤5。
这道题无法使用 KMP 解决,但是可以通过哈希 + 二分来解决。
枚举所有可能匹配的子串,假设现在枚举的子串为 s′,通过哈希 + 二分可以快速找到 s′ 与 p 第一个不同的位置。之后将 s′ 与 p 在这个失配位置及之前的部分删除掉,继续查找下一个失配位置。这样的过程最多发生 k 次。
通过哈希同样可以 O(n) 解决这个问题,具体方法就是记 Ri 表示以 i 作为结尾的最长回文的长度,那么答案就是 maxi=1nRi。考虑到 Ri≤Ri−1+2,因此我们只需要暴力从 Ri−1+2 开始递减,直到找到第一个回文即可。记变量 z 表示当前枚举的 Ri,初始时为 0,则 z 在每次 i 增大的时候都会增大 2,之后每次暴力循环都会减少 1,故暴力循环最多发生 2n 次,总的时间复杂度为 O(n)。
最长公共子字符串
问题:给定 m 个总长不超过 n 的非空字符串,查找所有字符串的最长公共子字符串,如果有多个,任意输出其中一个。其中 1≤m,n≤106。
很显然如果存在长度为 k 的最长公共子字符串,那么 k−1 的公共子字符串也必定存在。因此我们可以二分最长公共子字符串的长度。假设现在的长度为 k,check(k) 的逻辑为我们将所有所有字符串的长度为 k 的子串分别进行哈希,将哈希值放入 n 个哈希表中存储。之后求交集即可。
时间复杂度为 O(m+nlogn)。
确定字符串中不同子字符串的数量
问题:给定长为 n 的字符串,仅由小写英文字母组成,查找该字符串中不同子串的数量。
为了解决这个问题,我们遍历了所有长度为 l=1,⋯,n 的子串。对于每个长度为 l,我们将其 Hash 值乘以相同的 b 的幂次方,并存入一个数组中。数组中不同元素的数量等于字符串中长度不同的子串的数量,并此数字将添加到最终答案中。
为了方便起见,我们将使用 h[i] 作为 Hash 的前缀字符,并定义 h[0]=0。
参考代码
int count_unique_substrings(string const& s) { int n = s.size(); constexpr static int b = 31; constexpr static int m = 1e9 + 9; vector<long long> b_pow(n); b_pow[0] = 1; for (int i = 1; i < n; i++) b_pow[i] = (b_pow[i - 1] * b) % m; vector<long long> h(n + 1, 0); for (int i = 0; i < n; i++) h[i + 1] = (h[i] + (s[i] - 'a' + 1) * b_pow[i]) % m; int cnt = 0; for (int l = 1; l <= n; l++) { set<long long> hs; for (int i = 0; i <= n - l; i++) { long long cur_h = (h[i + l] + m - h[i]) % m; cur_h = (cur_h * b_pow[n - i - 1]) % m; hs.insert(cur_h); } cnt += hs.size(); } return cnt;}