【P8306 【模板】字典树】

P8306 【模板】字典树

题目描述

给定 nnn 个模式串 s1,s2,...,sns_1, s_2, \dots, s_ns1,s2,...,sn 和 qqq 次询问,每次询问给定一个文本串 tit_iti,请回答 s1∼sns_1 \sim s_ns1∼sn 中有多少个字符串 sjs_jsj 满足 tit_iti 是 sjs_jsj 的前缀

一个字符串 ttt 是 sss 的前缀当且仅当从 sss 的末尾删去若干个(可以为 000 个)连续的字符后与 ttt 相同。

输入的字符串大小敏感。例如,字符串 Fusu 和字符串 fusu 不同。

输入格式

本题单测试点内有多组测试数据

输入的第一行是一个整数,表示数据组数 TTT。

对于每组数据,格式如下:

第一行是两个整数,分别表示模式串的个数 nnn 和询问的个数 qqq。

接下来 nnn 行,每行一个字符串,表示一个模式串。

接下来 qqq 行,每行一个字符串,表示一次询问。

输出格式

按照输入的顺序依次输出各测试数据的答案。

对于每次询问,输出一行一个整数表示答案。

输入输出样例 #1

输入 #1

复制代码
3
3 3
fusufusu
fusu
anguei
fusu
anguei
kkksc
5 2
fusu
Fusu
AFakeFusu
afakefusu
fusuisnotfake
Fusu
fusu
1 1
998244353
9

输出 #1

复制代码
2
1
0
1
2
1

说明/提示

数据规模与约定

对于全部的测试点,保证 1≤T,n,q≤1051 \leq T, n, q\leq 10^51≤T,n,q≤105,且输入字符串的总长度不超过 3×1063 \times 10^63×106。输入的字符串只含大小写字母和数字,且不含空串。

说明

std 的 IO 使用的是关闭同步后的 cin/cout,本题不卡常。

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
struct node{
	char c;//字符 
	unordered_map<char,node*> hashx;//哈希表,子节点映射表,字符->子节点指针 
	node *fa;
	bool end;//字符串尾标记 
	int tot;//有多少个分支 
	node(char x=0):c(x),end(0),tot(0),fa(NULL){}//默认参数初始化节点 
};
class trie{
	private:
		node* root;//私有变量根节点 
		void destroy(node *cur){//递归销毁字典树占有内存 
			for(unordered_map<char,node*>::iterator it=cur->hashx.begin();it!=cur->hashx.end();it++)//遍历节点的哈希表 
				destroy(it->second);//先销毁该节点子节点, 
			delete cur;//再删除当前节点 
		}
		bool deletex(const string& s,node *&cur,int idx){//删除字典树中的当前字符串 
			if(s.size()<=idx){//递归基,遍历完了该字符串 
				if(!cur->end)return 0;//无结束标记,不删 
				cur->end=0;//否则取消结束标记
				return cur->hashx.empty();//空了,无子节点,可删 
			}
			if(cur->hashx.find(s[idx])==cur->hashx.end())return 0;//无该字符,不删
			bool k=deletex(s,cur->hashx[s[idx]],idx+1);//递归查询子节点,返回值决定该节点能不能删
			if(k){//可以删 
				delete cur->hashx[s[idx]];//删除该节点idx对应子节点 
				cur->hashx.erase(s[idx]);//从本节点哈希表中删除该元素
				return cur->hashx.empty()&&!cur->end;//当前节点无后代节点且非尾节点,则可删 
			} 
			return 0;//意思是子节点不可删,当前节点也保留。没有也正确。 
		}
	public:
		trie(){root=new node();}//初始化时完成私有变量root的初始化 
		~trie(){destroy(root);}//析构函数,释放内存 
		void insert(const string& s){//往字典树插入字符串s 
			node *fa=root,*cur;
			int i=0;
			for(;i<s.size();i++){
				if(fa->hashx.find(s[i])==fa->hashx.end()){//如果没该字符就往哈希表添加元素,并指向对应节点
					cur=new node(s[i]);
					cur->fa=fa;
					fa->hashx[s[i]]=cur;
				}
				fa=fa->hashx[s[i]];//进入下一层节点 
				fa->tot++;
			}
			fa->end=1;//标记字符串尾标记 
		}
		bool search(const string& s){//查询字典树中是否有该字符串 
			node *cur=root;//根节点 
			for(int i=0;i<s.size();i++){
				if(cur->hashx.find(s[i])==cur->hashx.end())return 0;//没有该字符 
				cur=cur->hashx[s[i]];//查询下个字符 
			}
			return cur->end;//判断是否有完整字符串尾标记 
		}
		int count_pre(const string& s){//查询字典树中是否有该字符串 
			node *cur=root;//根节点 
			for(int i=0;i<s.size();i++){
				if(cur->hashx.find(s[i])==cur->hashx.end())return 0;//没有该字符 
				cur=cur->hashx[s[i]];//查询下个字符 
			}
			return cur->tot;//判断是否有完整字符串尾标记 
		}
		bool remove(const string& s){//从字典树中删除该字符串 
			return deletex(s,root,0);//调用私用函数完成删除 
		}
		void view(){//层序打印该字典树 
			cout<<"字典树\n";
			node *cur=root;//根节点 
			int cur_level=0;//当前层数 
			queue<pair<node*,int>> q;	q.push({cur,cur_level});//宽搜队列,并初始化 
			while(!q.empty()){//层序遍历循环 
				int level=q.front().second;//该节点深度 
				cur=q.front().first;q.pop();//队首元素 
				if(cur_level<level){cout<<endl;cur_level=level;}//深度变化则换行 
				for(unordered_map<char,node*>::iterator it=cur->hashx.begin();it!=cur->hashx.end();it++){//节点内哈希表的每元素 
					if(it->second->end)cout<<'*'; 
					cout<<it->second->c<<","<<it->second->tot<<"\t";q.push({it->second,level+1});//输出容器内元素第二部分------节点,带深度插入队列 
				}
			}
			cout<<"________________\n";
		}
};
string s;
int t,
	n,
	q; 
int main(){
	//freopen("data.cpp","r",stdin);
	cin>>t;
	while(t--){
		trie pre;//本生命周期结束就运行析构函数,自动销毁 
		cin>>n>>q;
		for(int i=0;i<n;i++){
			cin>>s;
			pre.insert(s);
			//pre.view();
		}
		for(int i=0;i<q;i++){
			cin>>s;
			cout<<pre.count_pre(s)<<endl;
		}
	}
	
	
	return 0;
}

trie字典树的优势

1.极致的前缀匹配效率,字符串插入、查询的时间复杂度仅与查询字符串的长度有关。O(len)

2.空间高效的前缀压缩,通过共享公共前缀大幅节省存储空间。

3.支持字典序遍历,无需额外排序。

4.扩展能力强,适合大规模字符串集合。

相关推荐
贾斯汀玛尔斯7 分钟前
每天学一个算法--倒排索引(Inverted Index)
算法·inverted-index
小e说说10 分钟前
打破偏科困境:这些学习软件助孩子重燃学习热情
算法
月昤昽1 小时前
autoCAD二次开发 4.正多边形与collection区分
算法·c#·二次开发·autocad二次开发
休息一下接着来1 小时前
C++ 固定容量环形队列实现
c++·算法
im_AMBER2 小时前
手撕hot100之矩阵!看完这篇就AC~
javascript·数据结构·线性代数·算法·leetcode·矩阵
笨笨饿2 小时前
#79_NOP()嵌入式C语言中内联汇编宏的抽象封装模式研究
linux·c语言·网络·驱动开发·算法·硬件工程·个人开发
如君愿2 小时前
考研复习 Day 30 | 习题--计算机网络 第五章(运输层 上)、数据结构 图(上)
数据结构·计算机网络·课后习题
weixin_421725262 小时前
C语言中volatile关键字怎么用C语言volatile在多线程中的作用
c语言·数据结构·运算符优先级·变量命名·volatile关键字
风萧萧19992 小时前
问答样例如何在RAG问答中使用?
算法
七夜zippoe3 小时前
DolphinDB分区策略:HASH分区与COMPO分区
算法·哈希算法·hash·dolphindb·compo