P : [算法竞赛进阶指南]前缀统计
Time Limit:2 Sec Memory Limit:128 MiB
Back Submit Edit
Description
给定N个字符串S1,S2…SN,接下来进行M次询问,每次询问给定一个字符串T,求S1~SN中有多少个字符串是T的前缀。输入字符串的总长度不超过106,仅包含小写字母。
字符串 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
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=1000010;
int trie[maxn][26];//定义字典树因为输入总长度不超过1e6所以建立一个行数为maxn行的字典数组
//这里要理解字典数组的每后一位都是有26种情况的也就是26叉树
int cnt[maxn];//记录每一行的结尾的字符串的个数
char str[maxn];
int n,m,idx;
void insert(){
int p=0;
for(int i=0;str[i];i++)
{
int &c=trie[p][str[i]-'a'];//先判断本位置的索引是否为空
if(!c)c=++idx;//++idx的作用是字典树的根是空的
//这里实现了两个操作
//首先由上一个字符得到的索引p(行数),来到索引出
//查看当前字符是否存在
//若存在(即存在索引,有指向),直接获得索引
//若没有存在(不存在索引),c=++idx建立新索引并且指向这是因为c是指针
//若为空要在整体的字典树中增加一行来建立新的一个字符串
//这里要理解的是当只要插入的字符串为新字符串时
//就要越过所有插入过的字符串新建立一个索引
//这是为了保持字符串的单端指向性即每一个字符串中每一个字符都应该是
//有唯一前端指向和唯一后端指向的
//并且要理解的是字典树中的索引是行数
//如果不是这样的操作而是只要本位置没有索引就将本位置的索引赋值给上一个字符的话
//那么必将出现错误
//这样导致的结果是
//每一次输入一个字符串就会先判断第一行有没有没有就插入然后就是第二行……
//这样的操作只是将所有字符串的第一个字符存在第一行
//第二个的字符串存在了第二行……
//在判断的时候就不会有字符串的单端指向性了
//例如:
//得到的第二行有一个字符'a'但是不能知道是那个字符串得来的
//这里强调的是当有不同的字符串出现时一定要在整体字典树数组上增加一行来存储
p=c;
}
cnt[p]++;//当插入结束时给索引加一(即p行结束的字符串的个数)
}
int query(){
int p=0,sum=0;
for(int i=0;str[i];i++)
{
//这里是进行这样的操作
//先由字典树中获得本字符的下一个索引
//又因为字典树的单端指向性只要遇到下一个索引不存在时就表示
//该序列的字符串在存储的字典树中是不存在的
int &c=trie[p][str[i]-'a'];
if(!c)break;//没有索引表示在插入的字符串只是符合了前边的顺序,在这里结束了或者是指向了不同的字符
p=c;//获得新的索引
sum+=cnt[p];//当该位置结束时才有数值,也表示存在一个它的前缀
}
return sum;
}
int main(){
//freopen("qwe.txt","r",stdin);
cin>>n>>m;
for(int i=0;i<n;i++)
{
scanf("%s",str);
insert();
}
for(int i=0;i<m;i++)
{
scanf("%s",str);
printf("%d\n" ,query());
}
}