字典树(Trie)

  • 字典树

例题

【模板】字典树

原理

本质上是存储若干个字符串的公共前缀以减少重复的查询操作,达到提高查询效率的目的。

(本题中的字符串只含数字与大小写字母,所以一共有62个字符)

\(N\) 为字符串最大长度。

ch[N][65]ch[p][j]存储从节点 \(p\) 沿着值为 \(j\) 的这条边走到的子节点,从 'a''z''A''Z''0''9' 一共 \(62\) 个字符,对应映射值 \(0 - 61\) ,每个节点最多 \(62\) 个分叉。

cnt[N]cnt[p] 用来存储节点 \(p\) 在插入操作中被访问的次数

idxidx 用于给节点编号。

插入操作

初始阶段仅有一个空节点,编号为 \(0\),不存储任何数据。从根节点开始插入,枚举字符串的每个字符,如果当前节点有子节点是待插入的下一个字符,就走到子节点。如果没有满足的子节点,就先创建子节点,然后走到这个子节点。每次走到子节点的时候,就将这个节点的计数器 \(+1\)

查询操作

从根开始查,扫描字符串。如果有字符 s[i],则继续往下走,能走到词尾,则返回访问次数;无字符 s[i],则返回 \(0\).

代码

#include <iostream>
#include <cstring>
using namespace std;
const int N = 3000005;
char str[N];
int ch[N][65], cnt[N], idx;

int getnum(char c)
{//映射字符
	if (c >= 'a' && c <= 'z')return c - 'a';
	else if (c >= 'A' && c <= 'Z')return c - 'A' + 26;
	else if (c >= '0' && c <= '9')return c - '0' + 52;
	else return -1;
}


void insert(char s[])
{//插入操作
	int p = 0;
	for (int i = 0; s[i]; i++)
	{
		int j = getnum(s[i]);
		if (!ch[p][j])ch[p][j] = ++idx;
		p = ch[p][j];
		cnt[p]++;//将cnt[p]++放在循环内部是因为题目要求查询前缀中含有待查询序列的字符串
	}
	//如果目的是查询与被查询字符串相等的字符串的个数,就将插入操作改为
	/*
	int p = 0;
	for (int i = 0; s[i]; i++)
	{
		int j = getnum(s[i]);
		if (!ch[p][j])ch[p][j] = ++idx;
		p = ch[p][j];
	}
	cnt[p]++;
	*/
}

int query(char s[])
{//查询操作
	int p = 0;
	for (int i = 0; s[i]; i++)
	{
		int j = getnum(s[i]);
		if (!ch[p][j])return 0;
		p = ch[p][j];
	}
	return cnt[p];
}

void solve()
{
	int n, q;
	cin >> n >> q;

	//清空之前使用过的数组
	for (int i = 0; i <= idx; i++)
	{
		for (int j = 0; j < 65; j++)ch[i][j] = 0;
		cnt[i] = 0;
	}
	idx = 0;


	for (int i = 1; i <= n; i++)
	{
		cin >> str;
		insert(str);
	}
	for (int i = 1; i <= q; i++)
	{
		cin >> str;
		cout << query(str) << endl;
	}
}

int main()
{
	ios::sync_with_stdio(0);
	int T;
	cin >> T;
	while (T--)
	{
		solve();
	}
	return 0;
}
posted @ 2023-08-16 01:55  susenyang  阅读(40)  评论(0)    收藏  举报  来源