P8306 【模板】字典树 题解

题目

题目描述

给定 n n n 个模式串 s 1 , s 2 , … , s n s_1, s_2, \dots, s_n s1,s2,,sn q q q 次询问,每次询问给定一个文本串 t i t_i ti,请回答 s 1 ∼ s n s_1 \sim s_n s1sn 中有多少个字符串 s j s_j sj 满足 t i t_i ti s j s_j sj前缀

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

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

输入格式

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

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

对于每组数据,格式如下:
第一行是两个整数,分别表示模式串的个数 n n n 和询问的个数 q q q
接下来 n n n 行,每行一个字符串,表示一个模式串。
接下来 q q q 行,每行一个字符串,表示一次询问。

输出格式

按照输入的顺序依次输出各测试数据的答案。
对于每次询问,输出一行一个整数表示答案。

样例 #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 ≤ 1 0 5 1 \leq T, n, q\leq 10^5 1T,n,q105,且输入字符串的总长度不超过 3 × 1 0 6 3 \times 10^6 3×106。输入的字符串只含大小写字母和数字,且不含空串。



题解

这道题算是 普及/提高- 的题里面通过率较低的一道题了。


首先的首先(可跳过)

Trie树是什么?

“Trie” 翻译过来是 “排序” 的意思。

所以说———— “排序树” ?

好吧,都叫它 “字典树” 。

而它是干什么用的呢?


如果给你一堆字符串,然后问你某一个字符串是否存在,怎么办?

暴力枚举出…………(想多了)。

所以,这就需要用到trie树了。

如下图:

Trie树示意图.png

Trie树是一棵树。

根节点 r o o t root root 为空,其它所有的子节点都包含了一个字符。

每个节点有一些不同的子节点。

这样,如果你想要在其中查找字符串 “ecea” 是否出现过,就只用从根节点开始,依次查询每一个对应的节点,就可以了。如果查询到某一个节点找不到对应的子节点,就说明不存在这个字符串;如果能查询完,这个字符串就是存在的。

如下图:

Trie树示意图2.png

此外,我们还需要在每个字符串的末尾打上一个标记,来判断是否有以这个字符结尾的字符串。


再来看看这个题

给定 n n n 个模式串 s 1 , s 2 , … , s n s_1, s_2, \dots, s_n s1,s2,,sn q q q 次询问,每次询问给定一个文本串 t i t_i ti,请回答 s 1 ∼ s n s_1 \sim s_n s1sn 中有多少个字符串 s j s_j sj 满足 t i t_i ti s j s_j sj前缀

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

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

题目要求的是判断前缀。也要打标记 + + + 插入 + + + 查询。

再来细讲一下

1. Trie树的存储实现

  • s o n son son 数组用来存每个节点的子节点信息, s o n [ i ] [ j ] son[i][j] son[i][j] 表示节点 i i i 有子节点 j j j

  • c n t cnt cnt 数组用来打标记, c n t [ i ] cnt[i] cnt[i] 记录以节点 i i i 结尾的字符串个数。

  • i d x idx idx 表示节点总数,在插入的时候每次 + + ++ ++

const int N = 1000010;
int son[3 * N][65], cnt[3 * N], idx;

2. Trie树的每个子节点都只含大小写字母和数字

题目有要求:

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

所以,写一个字符转换成数值的函数,方便存储。

int ctoi(char c){
    
    
	int x;
	if('A' <= c && c <= 'Z') x = c - 'A';				//大写字母对应 0 ~ 25
	else if('a' <= c && c <= 'z') x = c - 'a' + 26;		//小写字母对应 26 ~ 51,所以要减'a',再加上26
	else x = c - '0' + 52;								//数字对应 52 ~ 61,同样的,减'0'再加上52
	return x;
}

3. 插入字符串

在Trie树中插入一个字符串,也写一个函数。

void insert(char str[]){
    
    
	int p = 0;							//p用来暂时记录当前的节点编号
	for(int i = 0; str[i]; ++i){
    
    		//循环字符串中的每一个字符
		int u = ctoi(str[i]);			//u是子节点的值
		if(!son[p][u]) son[p][u] = ++idx;	
        //如果当前节点没有此子节点,新拓展一个子节点,并把节点总数加一,也作为新节点的编号
		p = son[p][u];					//记录当前节点编号
		++cnt[p];						//打标记,不再赘述
	}
} 

4.查询字符串

和插入类似。

int query(char str[]){
    
    
	int p = 0;							//同插入中的p
	for(int i = 0; str[i]; ++i){
    
    		//同插入
		int u = ctoi(str[i]);			//同插入
		if(!son[p][u]) return 0;		//如果没有对应的子节点,也就是找不到,直接返回0
		p = son[p][u];					//同插入
	}
	return cnt[p];
    //返回字符串个数。这里注意,如果查找到了,但是没有打标记,返回的cnt[p]还是0
}

最后的最后(Code)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1000010;

char str[3 * N];
int son[3 * N][65],cnt[3 * N],idx;

int ctoi(char c){
    
    
	int x;
	if('A' <= c && c <= 'Z') x = c - 'A';
	else if('a' <= c && c <= 'z') x = c - 'a' + 26;
	else x = c - '0' + 52;
	return x;
}

void insert(char str[]){
    
    
	int p = 0;
	for(int i = 0; str[i]; ++i){
    
    
		int u = ctoi(str[i]);
		if(!son[p][u]) son[p][u] = ++idx;
		p = son[p][u];
		++cnt[p];
	}
} 

int query(char str[]){
    
    
	int p = 0;
	for(int i = 0; str[i]; ++i){
    
    
		int u = ctoi(str[i]);
		if(!son[p][u]) return 0;
		p = son[p][u];
	}
	return cnt[p];
}
int t;
int main(){
    
    
	scanf("%d",&t);
	while(t--){
    
    
		for(int i = 0; i <= idx; ++i)
            for(int j = 0; j <= 64; ++j)
                son[i][j] = 0;
        for(int i = 0; i <= idx; i++)
            cnt[i] = 0;
        //每组数据前清空数组
		idx = 0;
		int n,q;
		scanf("%d%d", &n, &q);
		while(n--){
    
    
			scanf("%s", str);
			insert(str);//插入
		}
		while(q--){
    
    
			scanf("%s", str);
			printf("%d\n", query(str));//查询
		}
	}
	return 0;
}

真的最后了?

这是蒟蒻的第一篇题解,很详细了(自信)

请多多支持。

Thanks

猜你喜欢

转载自blog.csdn.net/dcczzzm/article/details/125953207