【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.扩展能力强,适合大规模字符串集合。

相关推荐
Wenhao.1 小时前
LeetCode Hot100 腐烂的橘子
算法·leetcode·职场和发展
行走的bug...1 小时前
支持向量机
算法·机器学习·支持向量机
晨非辰1 小时前
算法闯关日记 Episode :解锁链表「环形」迷局与「相交」奥秘
数据结构·c++·人工智能·后端·python·深度学习·神经网络
信号处理学渣1 小时前
matlab之将一个升序数组按照元素值连续与否分成多组
数据结构·算法·matlab
大工mike2 小时前
代码随想录算法训练营第三十四天 | 198.打家劫舍 213.打家劫舍II 337.打家劫舍III
数据结构·算法·动态规划
用户992441031562 小时前
TRAE SOLO 赋能大模型工程化实践:从模型选型到安全部署的一站式实战指南
算法
goyeer2 小时前
05.[SAP ABAP] ABAP中的运算符
算法·sap·abap·运算符
NAGNIP2 小时前
面试官:BatchNorm、LayerNorm、GroupNorm、InstanceNorm 有什么本质区别?
算法·面试
Rock_yzh2 小时前
LeetCode算法刷题——560. 和为 K 的子数组
数据结构·c++·学习·算法·leetcode·职场和发展·哈希算法