题目

题目描述

给定

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