题目意思:
给一个待查询的字符串,输出包含这个字符串(作为子串)的个数。
数据量比较大,用 KMP 会超时。可以用 Trie 树,不过需要变型。
正常的 Trie 树是以前缀开始的。在这题需要改成以后缀开始,后缀指以字符串每个字符作为开始直至结尾。例如字符串 abcdae ,各个后缀是 abcdae,bcdae,cdae,dae,ae,e。这样就很方便查询子串出现的次数了。
但是要记录每个后缀它是属于哪个源字符串的。不然在 insert 时会重复计算。所以可以给每个结点增加一个 id 的字符,标识源字符串是第几个。
代码:
#include <cstdio>
#include <cstring>
#define maxn 100010 //10万
#define wlen 20 //单词长度
#define character 'a'
#define SIZE 26 //26个字母
typedef struct node {
int cnt; //记录访问量
int id; //记录是否同一个源字符串
int childs[SIZE];
void init()
{
id = -1;
cnt = 0;
memset(childs, 0, sizeof(childs));
}
} Node;
Node ns[maxn * wlen];
int root;
int node_cnt = 0; //trie 树的结点数
char input[30];
void insert(char *s, int id)
{//建立以后缀为主的 trie 树,例如字符串 abcdae ,各个后缀是 abcdae,bcdae,cdae,dae,ae,e
//避免重复计算,例如 abcdae 和 ae 两个子串。所以要增加一个 id 标识是否同一个源字符串。
int r = 0, index, child;
for (int i = 0; s[i] != '\0'; ++i) {
index = s[i] - character;
child = ns[r].childs[index];
if (child != 0) {
r = child;
}
else {
ns[++node_cnt].init();
ns[r].childs[index] = node_cnt;
r = node_cnt;
}
if (ns[r].id != id) ns[r].cnt++;
ns[r].id = id;
}
}
int query(char *s)
{
int r = 0;
int index, child;
for (int i = 0; s[i] != '\0'; ++i) {
index = s[i] - character;
child = ns[r].childs[index];
if (child == 0) {
return 0;
}
r = child;
}
return ns[r].cnt;
}
int main()
{
//freopen("testdata/2846.txt", "r", stdin);
int p, q;
ns[node_cnt = 0].init();
scanf("%d", &p);
while (p--) {
scanf("%s", input);
for (int i = 0; i < strlen(input); ++i) {
insert(input+i, p+1);
}
}
scanf("%d", &q);
while (q--) {
scanf("%s", input);
printf("%d\n", query(input));
}
return 0;
}