字典树
引入
字典树(trie),顾名思义就是在字典上建树用于维护字符串的前缀相关内容。
在字典树中,每条边都是一个字符,从根节点到任意一个节点都可以代表一个字符串(例如 \(1 \rightarrow 2 \rightarrow 6 \rightarrow 11\) 就代表了 \(aba\))。
模板题:P8306 【模板】字典树。
结构
大致结构如上图。
对于一个节点,它需要记录它的儿子的编号,必要时还需要记录这个节点在加入字符串的操作中访问了几次(即有几个字符串有这个前缀)。
令 trie[i][j]
表示节点 \(i\) 走一条边权为 \(j\) 后到达的节点编号,如果不存在则设为 \(0\)。
边权则使用离散化思想处理,例如 0~25
分别对应着 a~z
,26~51
表示 A~Z
等等。
加入字符串
令 id(char c)
表示 \(c\) 在离散化后的整数。
在处理新加入的字符串 \(s\) 时,从前往后枚举每一位,假设当前节点为 \(x\),处理到了字符串的第 \(i\) 位,那么如果 trie[x][id(s[i])]
为 \(0\),则新增一个节点,将 \(x\) 设为新增节点编号,否则 x = trie[x][id(s[i])]
。
int id (char c) { // 字符映射规则
return (c >= 'a' && c <= 'z' ? c - 'a' : (c >= 'A' && c <= 'Z' ? 26 + c - 'A' : 52 + c - '0'));
}
void Insert (string s) { // 加入一个字符串
int now = 0; // 当前节点编号
for (int i = 0; i < s.size(); i++) {
if (!trie[now][id(s[i])]) { // 没有对应节点
trie[now][id(s[i])] = ++cnt; // 新建节点
}
now = trie[now][id(s[i])], sum[now]++; // 跳过去,统计数量 + 1
}
}
查询字符串
与加入差不多,但当没有对应节点时不应该新建节点,而是直接返回 \(0\)。
int Query (string s) { // 查询有多少个字符串以 s 为前缀
int now = 0; // 当前节点编号
for (int i = 0; i < s.size(); i++) {
if (!trie[now][id(s[i])]) { // 没有对应节点
return 0; // 直接返回
}
now = trie[now][id(s[i])]; // 跳过去
}
return sum[now]; // 返回结果
}
模板题 Code
#include <iostream>
using namespace std;
const int N = 3e6 + 10;
int t, trie[N][65], sum[N], cnt, n, q;
string s;
int id (char c) { // 字符映射规则
return (c >= 'a' && c <= 'z' ? c - 'a' : (c >= 'A' && c <= 'Z' ? 26 + c - 'A' : 52 + c - '0'));
}
void Insert (string s) { // 加入一个字符串
int now = 0; // 当前节点编号
for (int i = 0; i < s.size(); i++) {
if (!trie[now][id(s[i])]) { // 没有对应节点
trie[now][id(s[i])] = ++cnt; // 新建节点
}
now = trie[now][id(s[i])], sum[now]++; // 跳过去,统计数量 + 1
}
}
int Query (string s) { // 查询有多少个字符串以 s 为前缀
int now = 0; // 当前节点编号
for (int i = 0; i < s.size(); i++) {
if (!trie[now][id(s[i])]) { // 没有对应节点
return 0; // 直接返回
}
now = trie[now][id(s[i])]; // 跳过去
}
return sum[now]; // 返回结果
}
void Solve () {
cnt = 0;
cin >> n >> q;
for (int i = 1; i <= n; i++) {
cin >> s;
Insert(s);
}
while (q--) {
cin >> s;
cout << Query(s) << '\n';
}
for (int i = 0; i <= cnt; i++) {
for (int j = 0; j < 62; j++) {
trie[i][j] = 0;
}
sum[i] = 0;
}
}
int main () {
ios::sync_with_stdio(0), cin.tie(0);
for (cin >> t; t; t--) {
Solve();
}
return 0;
}
应用
- 检索字符串,例如模板题。
- 01 字典树,但是不会。
- AC 自动姬,但是也不会。