NCSTOJ 1421 前缀统计 —— Trie

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhaohaibo_/article/details/83119696

Description

[字典树模版题] 给定N个字符串S1,S2…SN,接下来进行M次询问,每次询问给定一个字符串T,求S1~SN中有多少个字符串是T的前缀。输入字符串的总长度不超过10^6,仅包含小写字母。字符串 S1(不妨假设长度为 n)被称为字符串 S2 的前缀,当且仅当:S2 的长度不小于 n,且 S1 与 S2 前 n 个字符组组成的字符串完全相同。

Input

第一行两个整数N,M。接下来N行每行一个字符串Si。接下来M行每行一个字符串表示询问。

Output

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

Sample Input

3 2
ab
bc
abc
abc
efg

Sample Output

2
0

把这N个字符串插入一颗Trie树中,Trie的每个节点上存储一个整数cnt,记录该节点是多少个字符串的末尾节点。(为了处理插入重复字符串的情况,这里要记录个数,而不能只做结尾标记)

对于每个询问,在Trie树中检索T,在检索过程中累加途径的每个节点的cnt值,就是该询问的答案。

关于Trie树:本题需要插入和检索的字符串都由小写字母构成,所以Trie每个节点具有26个字符指针分别为a到z。

在Trie树中,字符数据都体现在树的边(指针)上,树的节点仅保存一些额外信息,例如单词结尾标记等。其空间复杂度为O(NC),其中N是节点个数,C是字符集的大小。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int SIZE=1000010;
// trie每个节点记录cnt, 根节点为1
int trie[SIZE][26], cnt = 1; 
// 存在重复字符串
int edTot[SIZE];
int n, m;
char str[SIZE];

// 向Trie中插入一个字符串
void insert(char* str) { 
    // 当需要插入一个字符串str时,令一个指针p起初指向根节点。然后,依次扫描str中的每个字符c。
    int p = 1;
    // 一个一个单词插入
    for (int k = 0; k < strlen(str); k++) {
        int ch = str[k]-'a';
        // 如果p的ch字符指针指向空,则新建一个节点Q,令p的c字符指针指向Q
        if (!trie[p][ch])
            trie[p][ch] = ++cnt;
        // 令P = Q
        p = trie[p][ch];
    }
    edTot[p]++;
}

int search(char* str) {
    int p = 1;
    int ans = 0;
    for (int k = 0; k < strlen(str); k++) {
        p = trie[p][str[k]-'a'];
        if (p == 0) return ans;
        ans += edTot[p];
    }
    return ans;
}

int main() {
    cin>>n>>m;
    for(int i=1;i<=n;i++) {
        scanf("%s",str);
        insert(str);
    }
    for(int i=1;i<=m;i++) {
        scanf("%s",str);
        printf("%d\n", search(str));
    }
}

猜你喜欢

转载自blog.csdn.net/zhaohaibo_/article/details/83119696