PineappleSummer
2024-11-04 16:15:17
修改记录:
转载请在文章显著处注明出处。
众所周知,字符串 Hash 是一种很好的暴力,但是,可以通过 Hash 实现 Trie 模板、KMP 模板,以及 Manacher 模板。
字符串哈希模板为 P3370 【模板】字符串哈希,在此不做讲解。
如果对 Trie 树有了解,一定知道 Trie 树上的一个节点代表了一个字符串前缀,Trie 树上的一条边代表了一个字符。
Trie 树上每个节点代表的字符串前缀是独一无二的,而一个字符串前缀所对应的哈希值也是独一无二的,考虑将 Trie 树上的节点替换为一个哈希值。
来看 Trie 树模版题 P8306 【模板】字典树。
给定
n 个模式串s_1, s_2, \dots, s_n 和q 次询问,每次询问给定一个文本串t_i ,请回答s_1 \sim s_n 中有多少个字符串s_j 满足t_i 是s_j 的前缀。
记
对于每次查询,记查询串的哈希值为
将 unordered_map
平均时间复杂度视为
对于 Hash,使用了自然溢出,CF 上可能卡掉。
这里给出 Hash 实现 Trie 的参考代码,可以通过 P8306 【模板】字典树:
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
#define umap unordered_map
const int base = 233;
int n, q, ans;
string str;
umap <ull, int> f;
void solve () {
cin >> n >> q; f.clear ();
for (int i = 1; i <= n; ++i) {
cin >> str; ull h = 0;
for (auto j : str) h = h * base + (ull) j, f[h] ++;
}
while (q--) {
cin >> str; ull h = 0; ans = 0;
for (auto i : str) h = h * base + (ull) i;
cout << f[h] << '\n';
}
}
signed main () {
ios::sync_with_stdio (false);
cin.tie (0); cout.tie (0);
int T; cin >> T;
while (T--) solve ();
return 0;
}
P3375 【模板】KMP
给出两个字符串
s_1 和s_2 ,若s_1 的区间[l, r] 子串与s_2 完全相同,则称s_2 在s_1 中出现了,其出现位置为l 。
现在请你求出s_2 在s_1 中所有出现的位置。对于字符串s_2 ,你还需要求出对于其每个前缀s' 的最长 bordert' 的长度。
首先看第一问,求
设
下文提到的
对于第二问,求出
设
那么退出循环时一定满足
由于
这里给出 Hash 实现 KMP 的参考代码,可以通过 P3375 【模板】KMP:
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const int N = 1e6 + 10;
const ull base = 233;
string s, t;
int n, m;
ull g[N], h[N], bs[N];
ull get1 (int l, int r) { return g[r] - g[l - 1] * bs[r - l + 1]; }
ull get2 (int l, int r) { return h[r] - h[l - 1] * bs[r - l + 1]; }
signed main () {
ios::sync_with_stdio (false);
cin.tie (0); cout.tie (0);
cin >> s >> t;
n = s.size (), m = t.size ();
s = " " + s; t = " " + t;
bs[0] = 1;
for (int i = 1; i < N; ++i) bs[i] = bs[i - 1] * base;
for (int i = 1; i <= n; ++i) g[i] = g[i - 1] * base + (ull) s[i];
for (int i = 1; i <= m; ++i) h[i] = h[i - 1] * base + (ull) t[i];
for (int i = 1; i <= n - m + 1; ++i)
if (get1 (i, i + m - 1) == h[m]) cout << i << '\n'; // 求出出现位置
cout << 0 << ' ';
for (int i = 2, j = 0; i <= m; ++i) {
while (j >= 0 && get2 (1, j + 1) != get2 (i - j, i)) j--;
j ++; cout << j << ' ';
}
return 0;
}
P3805 【模板】manacher
给出一个只由小写英文字符
\texttt a,\texttt b,\texttt c,\ldots\texttt y,\texttt z 组成的字符串S ,求S 中最长回文串的长度。字符串长度为n 。1\le n\le 1.1\times 10^7 。
哈希最大的优点是
设
可以发现
而
每次
对于如何判断一个子串是否为回文串,可以正着做一遍再反着做一遍哈希,看看子串前一半和后一半是否相同即可。
这里给出一份 Hash 实现 Manacher 的参考代码,可以通过 P3805 【模板】manacher:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int N = 1.1e7 + 10;
const int base = 233;
int n, r[N], ans;
string s;
ull h1[N], h2[N], bs[N];
ull get1 (int l, int r) { return h1[r] - h1[l - 1] * bs[r - l + 1]; }
ull get2 (int l, int r) { return h2[l] - h2[r + 1] * bs[r - l + 1]; }
signed main ()
{
ios::sync_with_stdio (false);
cin.tie (0); cout.tie (0);
cin >> s; n = s.size (); s = " " + s;
bs[0] = 1;
for (int i = 1; i <= n; i++) {
h1[i] = h1[i - 1] * base + ull (s[i]);
bs[i] = bs[i - 1] * base;
}
for (int i = n; i >= 1; i--) h2[i] = h2[i + 1] * base + ull (s[i]); // 反向哈希
for (int i = 1, j = 1; i <= n; i++) {
if (j != 1) j--;
while (j < i) {
int len = (i - j + 1) / 2;
if (get2 (j, j + len - 1) == get1 (i - len + 1, i)) // 是回文子串
break;
j++;
}
ans = max (ans, i - j + 1); // 更新答案
}
cout << ans << '\n';
return 0;
}
简要题意:给出字符串
s 和t ,需要从s 中不断删除t ,后面字符会向前补全,求最终的s 。
每次删除时一定有完整的
时间复杂度
这题 Hash 比 Trie 实现更加简单,推荐参考我的题解。
将
如果想要将
设
对于
对于 unordered_map
存一下。
大家对文章有什么好的建议欢迎在下面评论,有相关题目也可以提供,我会在修改时添加上贡献者,谢谢!