P8306 【模板】字典树

传送门

AtCoder 有题考了字典树,复习一下。

字典树概述

思考一个常见的问题:在 \(n\) 个字符串中,查找一个字符串,在不使用 map 或者其他容器的情况下怎么快速查找?

这时我们想想如何用字典查单词。假设我们要查找单词 code,那么要先翻到 c 部分,然后是其中的 o 部分,以此类推。

这一过程会形成一些分支,所以可以使用树形数据结构,于是字典树就诞生了。

字典树操作

建树

如何构建这一棵树?我们将根节点设为空,然后接下来以字典的方式逐步插入单词,即遇到相同的字母时,往下走,如果没有该节点,则分配一个节点给它。显然会出现一个节点被遍历到多次,所以此时可以用一个数组标记一下每个节点被遍历的次数,这就是一个以该节点为结尾的前缀的出现次数。

以上是逐步插入 cfcatox 三个单词的示意图。当然,实现时要将每个字符转成数字存储。

容易发现它可以查单词前缀,所以它又叫前缀树。这在不少题目中都有所考察。

查询

根据示意图可以发现查询时依然与建树时类似,从第一个字母开始,如果找得到就继续往下走,否则直接结束程序。

参考代码

接下来是以上两种操作的示例代码。

存储信息:

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; 
}

这题卡空间。

习题

  1. 于是他错误的点名开始了。
  2. [ABC411D] Conflict 2。
  3. 电子字典。
  4. [USACO12DEC] First! G。

本文参考了《算法竞赛》。希望对大家有所帮助。

posted @ 2025-07-20 10:48  Tiger_Rory  阅读(237)  评论(0)    收藏  举报
//雪花飘落效果