P8306 【模板】字典树
AtCoder 有题考了字典树,复习一下。
字典树概述
思考一个常见的问题:在 \(n\) 个字符串中,查找一个字符串,在不使用 map 或者其他容器的情况下怎么快速查找?
这时我们想想如何用字典查单词。假设我们要查找单词 code,那么要先翻到 c 部分,然后是其中的 o 部分,以此类推。
这一过程会形成一些分支,所以可以使用树形数据结构,于是字典树就诞生了。
字典树操作
建树
如何构建这一棵树?我们将根节点设为空,然后接下来以字典的方式逐步插入单词,即遇到相同的字母时,往下走,如果没有该节点,则分配一个节点给它。显然会出现一个节点被遍历到多次,所以此时可以用一个数组标记一下每个节点被遍历的次数,这就是一个以该节点为结尾的前缀的出现次数。

以上是逐步插入 cf,cat,ox 三个单词的示意图。当然,实现时要将每个字符转成数字存储。
容易发现它可以查单词前缀,所以它又叫前缀树。这在不少题目中都有所考察。
查询
根据示意图可以发现查询时依然与建树时类似,从第一个字母开始,如果找得到就继续往下走,否则直接结束程序。
参考代码
接下来是以上两种操作的示例代码。
存储信息:
struct TRIE {
int son[70]; //存62个子节点的编号(因为大小写及数字都存)
int cnt; //考虑到有的单词前缀会反复出现,为了节省空间就标记一下前缀出现次数,而非反复插入son数组
bool ok; //是否结束一个单词,这题可以不要
}t[N];
然后插入单词建树:
void Insert(char str[]) {
int id = 0;
int len = strlen(str); //strlen求长度
for(int i = 0; i < len; i++) {
int num = get(str[i]); //转数字
if(!t[id].son[num]) t[id].son[num] = ++idx; //分配新位置
id = t[id].son[num]; //顺着树走下去
t[id].cnt++; //前缀出现次数增加
if(i == len - 1) t[id].ok = 1; //是否结束一个单词,这题可以不写
}
}
查找:
int Find(char str[]) {
int id = 0;
int len = strlen(str);
for(int i = 0; i < len; i++) {
int num = get(str[i]);
if(!t[id].son[num]) return 0; //查不到
id = t[id].son[num]; //顺着树往下走
}
return t[id].cnt; //前缀出现次数
}
完整代码
#include<bits/stdc++.h>
using namespace std;
const int N = 3e6 + 5;
int n, q, idx;
char s[N];
struct TRIE {
int son[70]; //存62个子节点的编号(因为大小写及数字都存)
int cnt; //考虑到有的单词前缀会反复出现,为了节省空间就标记一下前缀出现次数,而非反复插入son数组
//bool ok; //是否结束一个单词,这题可以不要
}t[N];
int get(char x) {
if(x >= 'A' && x <= 'Z') return x - 'A' + 1;
if(x >= 'a' && x <= 'z') return x - 'a' + 27;
return x - '0' + 55;
}//映射字符
void Insert(char str[]) {
int id = 0;
int len = strlen(str); //strlen求长度
for(int i = 0; i < len; i++) {
int num = get(str[i]);
if(!t[id].son[num]) t[id].son[num] = ++idx; //分配新位置
id = t[id].son[num]; //顺着树走下去
t[id].cnt++; //前缀出现次数增加
//if(i == len - 1) t[id].ok = 1; //是否结束一个单词
}
}//插入
int Find(char str[]) {
int id = 0;
int len = strlen(str);
for(int i = 0; i < len; i++) {
int num = get(str[i]);
if(!t[id].son[num]) return 0; //查不到
id = t[id].son[num];
}
return t[id].cnt; //前缀出现次数
}
int main(){
//cin.tie(0) -> sync_with_stdio(0); cout.tie(0);
int T; cin >> T;
while(T--) {
cin >> n >> q;
for(int i = 0; i <= idx; i++) for(int j = 0; j < 65; j++) t[i].son[j] = 0; //多测要清空,memset可能会被卡
for(int i = 0; i <= idx; i++) t[i].cnt = 0;
idx = 0;
for(int i = 1; i <= n; i++) {
scanf("%s", s);
Insert(s);
}
for(int i = 1; i <= q; i++) {
scanf("%s", s);
cout << Find(s) << '\n';
}
}
return 0;
}
这题卡空间。
习题
本文参考了《算法竞赛》。希望对大家有所帮助。

浙公网安备 33010602011771号