用法:
用来高效地存储和查找存储字符串集合。
插入abc:
模板:
int son[N][26], cnt[N] , idx;
//0号点既是根节点,又是空节点
//son[][]存储树中每个节点的子节点
//cnt[]存储以每个节点结尾的单词数量
//插入一个字符串
void insert(char *str)
{
int i;
int p = 0; // 根节点为0
for(i = 0; str[i] ; i ++)
{
int u = str[i] - 'a'; // 使a到z映射到0到25
if(!son[p][u]) son[p][u] = ++ idx; // 如果p这个节点不存在u这个儿子的话,则创建一个新的节点
p = son[p][u]; // 父结点变了
}
cnt[p] ++; // 以p为节点结尾的单词数量
}
//查询字符串出现的次数
int query(char *str)
{
int i;
int p = 0;
for(i = 0; str[i] ; i ++)
{
int u = str[i] - 'a'; // 使a到z映射到0到25
if(!son[p][u]) return 0; // 找不到,字符串出现次数就是0
p = son[p][u];
}
return cnt[p];
}
题目1:(Trie字符串统计)
维护一个字符串集合,支持两种操作:
I x
向集合中插入一个字符串 x;
Q x
询问一个字符串在集合中出现了多少次。
共有 N个操作,输入的字符串总长度不超过 105,字符串仅包含小写英文字母。
输入格式
第一行包含整数 N,表示操作数。
接下来 N行,每行包含一个操作指令,指令为 I x
或 Q x
中的一种。
输出格式
对于每个询问指令 Q x
,都要输出一个整数作为结果,表示 x在集合中出现的次数。
每个结果占一行。
数据范围
1≤N≤2∗104
输入样例:
5
I abc
Q abc
Q ab
I ab
Q ab
输出样例:
1
0
1
AC代码:
#include <iostream>
using namespace std;
const int N = 100010;
int son[N][26], cnt[N], idx;
char str[N];
void insert(char *str)
{
int p = 0;
for (int i = 0; str[i]; i ++ )
{
int u = str[i] - 'a';
if (!son[p][u]) son[p][u] = ++ idx;
p = son[p][u];
}
cnt[p] ++ ;
}
int query(char *str)
{
int p = 0;
for (int i = 0; str[i]; i ++ )
{
int u = str[i] - 'a';
if (!son[p][u]) return 0;
p = son[p][u];
}
return cnt[p];
}
int main()
{
int n;
scanf("%d", &n);
while (n -- )
{
char op[2];
scanf("%s%s", op, str);
if (*op == 'I') insert(str);
else printf("%d\n", query(str));
}
return 0;
}
题目2:(最短前缀)
题目描述:
给你若干个单词的字典,求字典中每一个单词的最短唯一前缀。最短唯一前缀:找出这个单词中的一个前缀,要求这个前缀只在这个单词中出现过,并且要求这个前缀最短。
输入:多组输入字符串,以eof为结尾
输出:输出其前缀
样例:
Inputcopy | Outputcopy |
---|---|
carbohydrate cart carburetor caramel caribou carbonic cartilage carbon carriage carton car carbonate | carbohydrate carboh cart cart carburetor carbu caramel cara caribou cari carbonic carboni cartilage carti carbon carbon carriage carr carton carto car car carbonate carbona |
解题思路:用各个单词构建字典树后,查询前缀出现一次的最早位置,具体看程序。
AC代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<map>
#define inf 0x3f3f3f3f
#define endl '\n'
const int N = 20010;
int son[N][26],cnt[N],idx; // cnt[]存储前缀出现的次数
char s[1005][26];
int k;
void insert(char *str)
{
int p = 0;
for(int i = 0 ; str[i] ; i ++)
{
int u = str[i] - 'a';
if(!son[p][u]) son[p][u] = ++ idx;
p = son[p][u];
cnt[p]++; // 前缀出现次数加+
}
}
void search(char *str)
{
int p = 0;
for(int i = 0 ; str[i] ; i ++)
{
int u = str[i] - 'a';
p = son[p][u];
printf("%c",str[i]);
if(cnt[p] == 1) break; // 前缀第一次出现
}
printf("\n");
}
int main()
{
while(~scanf("%s",s[k]))
{
insert(s[k]);
k ++;
}
for(int i = 0 ; i < k ; i ++)
{
printf("%s ",s[i]);
search(s[i]);
}
return 0;
}
题目3:
解1:
思路:
样例中前缀在含有前缀的字符串前边,还有一种可能就是字符串先输入,再输入其含有的前缀字符串。所以可以在插入函数中记录该字符出现的次数(如果 后缀后出现,后缀的最后尾字母次数大于1说明前边有字符串了)和标记最后一个字母的位置(标记可能出现的后缀)。标记的三个数组一定要大于十万,n最多有一万个字符串,每个字符串最好含10个字符。
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int son[N][26],cnt[N][26],vis[N][26];
char a[N][26];
int idx,flag;
void insert(int x)
{
int i,u;
int p = 0;
for(i = 0; a[x][i] ; i ++ )
{
u = a[x][i] - '0';
if(!son[p][u]) son[p][u] = ++ idx;
p = son[p][u];
vis[p][u] ++;
if(cnt[p][u] == 1) flag = 1;
}
if(vis[p][u] > 1) flag = 1;
cnt[p][u] = 1;
}
int main()
{
int i;
int t,n;
cin >> t;
while(t --)
{
idx = 0;
flag = 0;
memset(son,0,sizeof(son));
memset(cnt,0,sizeof(cnt));
memset(vis,0,sizeof(vis));
cin >> n;
for(i = 0 ; i < n ; i ++)
{
cin >> a[i];
insert(i);
}
if(flag == 1) cout << "NO" << endl;
else cout << "YES" << endl;
}
return 0;
}
解2:
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int son[N][26],cnt[N],flag,idx;
char a[N][26];
void insert(char *str)
{
int i;
int p = 0;
for(i = 0 ; str[i] ; i ++)
{
int u = str[i]-'0';
if(!son[p][u]) son[p][u] = ++idx;
p = son[p][u];
cnt[p]++;
}
}
bool check(char *str)
{
int i;
int p = 0;
for(i = 0 ; str[i] ; i++)
{
int u = str[i]-'0';
if(cnt[son[p][u]] == 1) return false;
p = son[p][u];
}
return true;
}
int main()
{
int i;
int t,n;
cin >> t;
while(t --)
{
idx = 0;
flag = 0;
memset(son,0,sizeof(son));
memset(cnt,0,sizeof(cnt));
cin >> n;
for(i = 0 ; i < n ; i ++)
{
cin >> a[i];
insert(a[i]);
}
for(i = 0; i < n ; i ++)
{
if(check(a[i]))
{
flag = 1;
break;
}
}
if(flag == 1) cout << "NO" << endl;
else cout << "YES" << endl;
}
return 0;
}
题目4:
题目描述:
给定N个字符串S1,S2…SNS_1,S_2 \dots S_NS1,S2…SN,接下来进行M次询问,每次询问给定一个字符串T,求S1∼SNS_1 \sim S_NS1∼SN中有多少个字符串是T的前缀。输入字符串的总长度不超过10610^6106,仅包含小写字母。
输入描述:
第一行两个整数N,M。接下来N行每行一个字符串Si。接下来M行每行一个字符串表示询问。
输出描述
对于每个询问,输出一个整数表示答案。
示例1
输入
3 2 ab bc abc abc efg
输出:
2 0
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int son[N][26],cnt[N],idx = 1;
char str[N];
void insert(char *str)
{
int i;
int p = 1;
for(i = 0; str[i] ;i ++)
{
int u = str[i] - 'a';
if(son[p][u] == 0) son[p][u] = ++ idx;
p = son[p][u];
}
cnt[p] ++;
}
int query(char *str)
{
int i;
int p = 1;
int res = 0;
for(i = 0; str[i] ;i++)
{
int u = str[i] - 'a';
p = son[p][u];
if(p == 0) return res;
res += cnt[p];
}
return res;
}
int main()
{
int n, m;
cin >> n >> m;
while(n --)
{
cin >> str;
insert(str);
}
while(m --)
{
cin >> str;
cout << query(str)<<endl;
}
return 0;
}