问题引入
给你n串字符串,求有多少字符串是其它字符串的前缀。
暴力搜索固然可以,但当n很大时就会超时,于是我们采用字典树的方法做。
介绍
字典树,又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。
它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
原理
想一想,如果给你一个单词"homo",在字典里查,你会如何查呢?
首先,你会先找到字母"h"分区,再在里面找到"o"分区,接着找"m"和"o",最终查到单词"homo"。
同理,字典树其实就是构建了一棵写着字符的树,使你查单词时就像在查字典一样。
例:有五个单词bb aa aaa aabb aaba
我们对其构建字典树。
其中红色表示有一个单词以此节点结尾。
于是,查单词aabb时,发现:a有,->a有,->b有,->b有,所以有单词aabb。
而查单词ab时会发现:a有,->b没有,所以没有单词ab。
代码实现
转字符
对于输入的东西,我们将它转换为字符串再处理。
e.g.
int tem=d[i]-'0';
构建树
对于一个字符串,例如ab。
我们发现a有了,于是不管它,第二次发现b没有,于是扩展出节点b。
同时,我们用idx记录子节点编号,再用数组记录红色节点。
e.g.
inline void insert(string d)
{
int p=0;
for(int i=0;i<d.size();i++)
{
int tem=d[i]-'0';
if(!f[p][tem])
f[p][tem]=++idx;
p=f[p][tem];
}
cnt[p]++;
}
解释:
f[i][j]表示第i号节点的子节点为j的子节点编号。
idx表示这个子节点的编号。
p表示当前在第几编号
cnt[i]表示有几个单词以编号i的子节点结尾。
显然,如果还没有这个子节点,就要创建它:
if(!f[p][tem])
f[p][tem]=++idx;
最后,用数组累记它:
cnt[p]++;
查找
对于一个单词,找它的第一位,如果有就继续,没有则退出。
e.g.
inline bool find(string d)
{
int p=0;
for(int i=0;i<d.size();i++)
{
int tem=d[i]-'0';
if(f[p][tem])
p=f[p][tem];
else
return false;
}
return true;
}
显然,如果连第i位都没找到,肯定没这个单词:
if(!f[p][tem])
return false;
反之,就继续:
if(f[p][tem])
p=f[p][tem];
例题
题目
小明是牛老大,某一天要带领奶牛们越狱.为了越狱方便,奶牛们可以互相发送秘密信息.
信息是二进制的,共有 M(1 <= M <= 50000)条,反间谍能力很强的约翰已经部分拦截了这些信息,知道了第 i 条二进制信息的前 b(i)(1 <= b(i) <=10000)位,他同时知道,奶牛使用 N(1 <= N <= 50000)条暗号.但是,他仅仅知道第 j 条暗号的前 c(j)(1 <= c(j) <= 10000)位。
对于每条暗号 j,他想知道有多少截得的信息能够和它匹配。也就是说,有多少信息和这条暗号有着相同的前缀。当然,这个前缀长度必须等于暗号和那条信息长度的较小者。
在输入文件中,位的总数(b(i)和c(i))不会超过 500000。
输入格式
第一行两个整数M和N
第二行到 M+1行:第 i+1行描述截获消息b(i)位,后跟 b(i)个空格分隔 0 和 1。
接下来是N行,每行第一个数是c(j),后跟c(j)个空格分割的0和1。
输出格式
共N行,每行输出一个数表示共有几条秘密消息与该暗号匹配
输入/输出例子1
输入:
4 5
3 0 1 0
1 1
3 1 0 0
3 1 1 0
1 0
1 1
2 0 1
5 0 1 0 0 1
2 1 1
输出:
1
3
1
1
2
样例解释
无
Code
#include<bits/stdc++.h>
using namespace std;
int n,m,x,y;
int cnt2[1000001],cnt1[1000001],f[100001][3],idx,f2[100001];
string s[50001],s1;
map<string,int>a;
inline void insert(string d)//建树
{
int p=0;
for(int i=0;i<d.size();i++)
{
int tem=d[i]-'0';//转字符
if(!f[p][tem])
f[p][tem]=++idx;
p=f[p][tem];
cnt2[p]++;
}
cnt1[p]++;
}
inline int find(string d,int z)//查找
{
int p=0,ans=0;
for(int i=0;i<d.size();i++)
{
int tem=d[i]-'0';
if(!f[p][tem])
return ans;
p=f[p][tem];
ans+=(cnt1[p]);
}
return ans+cnt2[p]-cnt1[p];
}
signed main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&x);
s[i]="";
for(int j=1;j<=x;j++)
scanf("%d",&y),s[i]+=(y+'0');
insert(s[i]);
}
for(int i=1;i<=m;i++)
{
scanf("%d",&x);
s1="";
for(int j=1;j<=x;j++)
scanf("%d",&y),s1+=(y+'0');
printf("%d\n",find(s1,i));
}
}
总结
其实字典树就是利用了公共前缀的一个树形结构,它可以用来处理许多事情,是很好的一个算法。