一、问题引入
给你 个字符串和
次询问,每次询问要求你给出以某个字符串为前缀的字符串个数。
二、解决问题
我们可以用已知的知识解决问题:
map | unordered_map | 暴力枚举 | |
---|---|---|---|
单次查询 | |||
插入 |
可以发现,暴力枚举的查询太慢了,而且 map 家族当 时会炸,所以也不行。
三、新算法:Trie
Trie 算法是专门解决字符串前缀查找问题的。
Trie 名叫字典树,是一种非常 有意思 的算法,它是在建树和搜索。
可以发现,按照线路走到一个根节点并将所经过的字符串联起来,就是一个在输入列表(左侧)里字符串。
这就是Trie。
四、存储
可以发现,这棵树是二十六叉树,那么存储方式就与二叉树类似。
int tree[N][26];
//tree[i][j] 表示编号为 i 的节点的字符为 j 的儿子的编号
字符按照顺序排列,0表示a、1表示b……25表示z。
五、插入查询
当我们有新字符串 acc 要加入时,我们可以按照字符串顺序走,a-c-c,走到第三个字符时,发现为空(0),那么可以把这个节点建出来,编号为 ++id。
查询也一样,只不过是在走到空节点时直接返回 0。
六、统计答案与复杂度分析
那么我们如何进行答案计算呢?
可以在插入的时候,将走过的节点权值+1,查询时返回终点节点的权值即可。
复杂度:O(|s|)。
七、完整代码和例题
这道题是完全相等,只需要在查询到终点节点时才权值+1。
#include <bits/stdc++.h>
using namespace std;
int tree[3000010][30];
int cnt[3000010],tot;
struct Trie{
void insert(string s){
int now=0;
for (auto c:s){
if (tree[now][c-'a']==0){
tree[now][c-'a']=++tot;
}now=tree[now][c-'a'];
}cnt[now]++;
}int ask(string s){
int now=0;
for (auto c:s){
if (tree[now][c-'a']==0) return 0;
now=tree[now][c-'a'];
}return cnt[now];
}
};
int main(){
int n,m;
cin>>n;
string s;
Trie t;
for (int i=1; i<=n; i++){
cin>>s; t.insert(s);
}cin>>m;
for (int i=1; i<=m; i++){
cin>>s;
if (t.ask(s)==0) cout<<"WRONG\n";//没有
else if (t.ask(s)>1) cout<<"REPEAT\n";//重复
else cout<<"OK\n",t.insert(s);//未重复,记录此次点名
}return 0;
}