AcWing 142 前缀统计

题目描述:
给定N个字符串S1,S2…SN,接下来进行M次询问,每次询问给定一个字符串T,求S1~SN中有多少个字符串是T的前缀。

输入字符串的总长度不超过106,仅包含小写字母。

输入格式

第一行输入两个整数N,M。接下来N行每行输入一个字符串Si。接下来M行每行一个字符串T用以询问。

输出格式

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

输入样例:

3 2
ab
bc
abc
abc
efg

输出样例:

2
0

分析:

本题考查字典树(Trie)。

首先简单拷贝下字典树的定义:Trie,又称字典树、单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

再拷贝下字典树的性质:

1.根节点不包含字符,除根节点外每一个节点都只包含一个字符。

2.从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。

3.每个节点的所有子节点包含的字符都不相同。

只看文字说明可能比较抽象,下面开始构建一棵字典树。开始初始化一个根结点,然后插入see,123层一次插入了see,然后插入pain,第一层没有p于是新建一个p结点,再依次插入ain;紧接着插入pand,发现12层有现成的pa于是在34层继续插入nd;最后插入dog,生成的字典树如下。由26个小写字母构成单词组成的字典树每个结点最多有26个孩子

字典树的形状并不难理解,问题是如何用代码去实现它。

首先,分析下字典树的插入:

void insert(){
    int p = 0;
    for(int i = 0;str[i];i++){
        int s = str[i] - 'a';
        if(!son[p][s])  son[p][s] = ++idx;
        p = son[p][s];
    }
    cnt[p]++;
}

这里有个二维数组son,第一维p表示结点的编号,第二维s表示当前结点是什么字母,将a - z映射到0 - 25.son[p][s]的值表示其孩子结点的编号。这里格外要注意的是,除了第一次插入的字符外(比如插入abc,第一次插入的是a),字典树其它层次的结点son数组的第一维均表示其编号,为啥第一次插入的字符不用编号而是用0作为p值呢?是为了充当索引的作用,在查询时能够很快地找到。以本题样例来进行说明:首先str = “ab”,插入的第一个节点为a,值为a - a = 0,首先判断下son[0][0]是否存在,不存在则插入,同时声明如果紧接着插入它的孩子结点,编号应该是1,于是继续插入b,编号为1,值为1,son[1][1] = 2表示如果接下来有孩子结点,编号应该是2;接着插入bc,发现son[0][1]不存在,于是插入,其孩子结点编号为3也就是c结点;最后插入abc,发现son[0][0]和son[1][1]均存在,于是紧接着son[1][1]后面插入字符c。这里的cnt数组是给每次插入的字符串的最后一个字符做上一个结束标记,方便查询时统计有多少单词数。(个人浅见,未参考其它文档,如有偏差,敬请指出)。

接着,分析下字典树的查询:

int search(){
    int p = 0,res = 0;
    for(int i = 0;str[i];i++){
        int s = str[i] - 'a';
        if(!son[p][s])  break;
        p = son[p][s];
        res += cnt[p];
    }
    return res;
}

 比如查询abc,从第一个字符开始,发现son[0][0]存在,于是判断下是不是有以当前遍历到的字符为结尾的单词,如果有,说明就是abc的前缀了,加上单词数;接着查询son[1][1],发现依然存在,而且有ab字符串,以b为结尾的字符有cnt数组的标记,于是加上单词数1;接着发现c在字典树里也有,于是又加上了单词数,此次查询找到了abc的前缀ab和abc。第二次查询efg,因为son[0][4] = 0.不存在索引结点为e,所以查询终止。

本题总的代码如下:

#include <iostream>
using namespace std;
const int maxn = 500000,maxm = 1000005;
int n,m;
int son[maxn][26],cnt[maxn],idx;
char str[maxm];
void insert(){
    int p = 0;
    for(int i = 0;str[i];i++){
        int s = str[i] - 'a';
        if(!son[p][s])  son[p][s] = ++idx;
        p = son[p][s];
    }
    cnt[p]++;
}
int search(){
    int p = 0,res = 0;
    for(int i = 0;str[i];i++){
        int s = str[i] - 'a';
        if(!son[p][s])  break;
        p = son[p][s];
        res += cnt[p];
    }
    return res;
}
int main(){
    scanf("%d%d",&n,&m);
    while(n--){
        scanf("%s",str);
        insert();
    }
    while(m--){
        scanf("%s",str);
        printf("%d\n",search());
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值