Trie 学习笔记

一、问题引入

给你 n 个字符串和 q 次询问,每次询问要求你给出以某个字符串为前缀的字符串个数。

二、解决问题

我们可以用已知的知识解决问题:

mapunordered_map暴力枚举
单次查询O(|s|\times log_{2}n)O(|s|\times c) O(\sum|s_{i}|)
插入O(|s|^2\times log_{2}n)O(|s|^2\times c)O(|s|)

可以发现,暴力枚举的查询太慢了,而且 map 家族当 |s| \ge 10^3 时会炸,所以也不行。

三、新算法:Trie

Trie 算法是专门解决字符串前缀查找问题的。

Trie 名叫字典树,是一种非常 有意思 的算法,它是在建树和搜索。

可以发现,按照线路走到一个根节点并将所经过的字符串联起来,就是一个在输入列表(左侧)里字符串。

这就是Trie。

四、存储

可以发现,这棵树是二十六叉树,那么存储方式就与二叉树类似。

int tree[N][26];
//tree[i][j] 表示编号为 i 的节点的字符为 j 的儿子的编号

字符按照顺序排列,0表示a、1表示b……25表示z。

五、插入查询

当我们有新字符串 acc 要加入时,我们可以按照字符串顺序走,a-c-c,走到第三个字符时,发现为空(0),那么可以把这个节点建出来,编号为 ++id。

 查询也一样,只不过是在走到空节点时直接返回 0。

六、统计答案与复杂度分析

那么我们如何进行答案计算呢?

可以在插入的时候,将走过的节点权值+1,查询时返回终点节点的权值即可。

复杂度:O(|s|)。

七、完整代码和例题

洛谷-P2580

这道题是完全相等,只需要在查询到终点节点时才权值+1。

#include <bits/stdc++.h>
using namespace std;
int tree[3000010][30];
int cnt[3000010],tot;
struct Trie{
	void insert(string s){
		int now=0;
		for (auto c:s){
			if (tree[now][c-'a']==0){
				tree[now][c-'a']=++tot;
			}now=tree[now][c-'a']; 
		}cnt[now]++;
	}int ask(string s){
		int now=0;
		for (auto c:s){
			if (tree[now][c-'a']==0) return 0;
			now=tree[now][c-'a']; 
		}return cnt[now];
	}
};
int main(){
	int n,m;
	cin>>n;
	string s;
	Trie t; 
	for (int i=1; i<=n; i++){
		cin>>s; t.insert(s);
	}cin>>m;
	for (int i=1; i<=m; i++){
		cin>>s;
		if (t.ask(s)==0) cout<<"WRONG\n";//没有
		else if (t.ask(s)>1) cout<<"REPEAT\n";//重复
		else cout<<"OK\n",t.insert(s);//未重复,记录此次点名
		
	}return 0;
} 

  • 12
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值