字符串哈希
字符串哈希
其实本质上就是将字符串映射到了一个值上面,由于比较数值是 \(O(1)\) 的,所以我们可以用这种方式加快字符串的比较。
转化方法:
选择一个比字符集大小要大的数 \(p\),将每个字符串看作成 \(p\) 进制下的数,求值。
通常来说,\(p\) 会取 \(31, 131, 1313, 13131, 131313\) 等。
这里,特别要注意,求出来的值是需要取模的,因为当字符集的大小足够大或者字符串的长度足够长时,转化出来的值会很大,如果直接存下来,会逐渐丧失 \(O(1)\) 比较大小的优势。
哈希方法
-
自然溢出方法:令 \(M = 2 ^ {64}\)(\(M\) 为模数),那么,可以直接用
unsigned long long
来存储数值,因为unsigned long long
所能表示的范围是 \(0 \sim 2 ^ {64} - 1\),刚好就是对 \(2 ^ {64}\) 取模后可能得到的余数的范围。 -
单哈希方法:自己选择模数取模。
-
双哈希方法:选择两个模数取模,得到两个 Hash 值,提高安全性。
获取子串的哈希值
令 \(n = |s|\), H(t)
表示 \(t\) 的 Hash 值, \(Hash_i\) 为 pre(s, i)
的 Hash 值。
很明显,我们可以 \(O(n)\) 求出 \(s\) 的所有前缀的 Hash 值,那么,我们可以用一种类似于前缀和的思想得到 \(s\) 中 \([l, r]\) 这个子串的 Hash 值。
比如说:\(H(de) = H(abcde) - H(abc) \times p ^ 2\)。
那么,可以写这样一个函数来获取任意一个子串的 Hash 值:
using ull = unsigned long long;
void get_P() {
for (int i = 1; i <= n; i++) {
P[i] = P[i - 1] * p % M;
}
}
ull get_Hash(int i, int j) {
return ((Hash[j] - Hash[i - 1] * P[j - i + 1]) % M + M) % M;
}
哈希求最长回文子串
暴力
枚举每个字符作为回文串的中心,分别向左边和右边进行扩展,时间复杂度为 \(O(|s| ^ 2)\)。
正解
枚举每个字符作为回文串的中心,二分左边和右边的长度,时间复杂度为 \(O(|s| \times \log |s|)\)
代码
#include <bits/stdc++.h>
using namespace std;
using ull = unsigned long long;
const int N = 1010;
string s;
int p = 131, n, ans;
ull P[N], a[N], b[N];
void get_P() {
P[0] = 1;
for (int i = 1; i <= n; i++) {
P[i] = P[i - 1] * p;
}
}
ull Hash(int l, int r, bool f) {
if (!f) {
return a[r] - a[l - 1] * P[r - l + 1];
}
return b[l] - b[r + 1] * P[r - l + 1];
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
getline(cin, s), n = s.size(), s = ' ' + s;
get_P();
for (int i = 1; i <= n; i++) {
a[i] = a[i - 1] * p + s[i];
}
for (int i = n; i >= 1; i--) {
b[i] = b[i + 1] * p + s[i];
}
for (int i = 1, l, r; i <= n; i++) {
if (i < n && s[i] == s[i + 1]) {
l = 0, r = min(i, n - i);
while (l < r) {
int mid = (l + r + 1) >> 1;
if (Hash(i - mid + 1, i, 0) == Hash(i + 1, i + mid, 1)) {
l = mid;
} else {
r = mid - 1;
}
}
ans = max(ans, 2 * l);
}
l = 0, r = min(i, n - i);
while (l < r) {
int mid = (l + r + 1) >> 1;
if (Hash(i - mid, i - 1, 0) == Hash(i + 1, i + mid, 1)) {
l = mid;
} else {
r = mid - 1;
}
}
ans = max(ans, 2 * l + 1);
}
cout << ans;
return 0;
}