字典树
∙ \bullet ∙什么是字典树(百度百科):又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
字典树的建立、查询、删除
建立,插入字符
已插入一个字符串为例,首先初始化一个二维数组
t
r
e
e
[
m
a
n
x
]
[
30
]
tree[manx][30]
tree[manx][30],
t
r
e
e
[
i
]
[
j
]
=
1
tree[i][j]=1
tree[i][j]=1(
j
j
j 是字符串里的某个字符的ASCII码减去字符’a’的值)就代表第
i
i
i 号结点有一条代表字符
j
+
′
a
′
j+'a'
j+′a′的边。
插入某个字符,如果
t
r
e
e
[
i
]
[
j
]
=
0
tree[i][j]=0
tree[i][j]=0则建立一新的结点
r
o
o
t
root
root作为当前第
i
i
i 号结点的子节点,并令
t
r
e
e
[
i
]
[
j
]
=
r
o
o
t
tree[i][j]=root
tree[i][j]=root,如果
t
r
e
e
[
i
]
[
j
]
!
=
0
tree[i][j]!=0
tree[i][j]!=0,就直接顺着走第
i
i
i 号节点的子节点,重复上述步骤,直至遍历完整个字符串。
有时还需再用一个 s u m [ ] sum[] sum[]数组来存具有相同前缀的字符串有多少个。
int cnt=0;
void insert_word(char *str)
{
int len = strlen(str);
int root = 0;
for(int i = 0 ; i < len ; i++){
int id = str[i] - 'a';
if(tree[root][id] == 0){
tree[root][id] = ++cnt;
}
root = tree[root][id];
sum[root]++;
}
return ;
}
查询字符串
原理和插入差不多,但是如果在某个结点处 t r e e [ i ] [ j ] = = 0 tree[i][j]==0 tree[i][j]==0那就肯定没有这个字符串了。 s u m [ r o o t ] sum[root] sum[root]既能知道有没有这个字符串,它存储的值又是以这个字符串为前缀的所有字符串的数量。
int query(char *str)
{
int len = strlen(str);
int root = 0;
for(int i = 0;i < len ;i++){
int id = str[i] - 'a';
if(tree[root][id] != 0){
root = tree[root][id];
}
else return 0;
}
return sum[root];
}
删除字符串
原理和上面两个一样,从根节点就是顺着走下去,每走到一个新的结点就 s u m [ r o o t ] − − sum[root]-- sum[root]−−
void delet(int n)
{
int root = 0;
for(int i = 30;i >= 0 ; i--)
{
int id = (n >> i) & 1;
root = tree [root] [id];
sum[root]--;
}
}
∙ \bullet ∙例题:
大多数考试中单选题目的选项都是ABCD四项。小Hi为了更高效地传递选项信息,将ABCD按如下方式编码:
A: 101, B:11, C:0, D:100
例如对于ABCD,编码后的01串是101110100。
现在给定一个编码后的01串,请你译出原本的选项串是什么。
Input
一个01串,长度不超过100000。
输入保证有唯一解。
Output
一个只包含ABCD的字符串代表答案。
Sample Input
101110100
Sample Output
ABCD
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <queue>
#include <vector>
#include <set>
#include <iostream>
#include <queue>
#include <string>
#include <math.h>
typedef long long LL;
using namespace std;
const int maxn=3e5+10;
int tree[maxn][3],sum[maxn];
char val[maxn];
char str[maxn];
int cnt = 0;
void insert_word(char *str,char s)
{
int len = strlen(str);
int root = 0;
for(int i = 0 ; i < len ; i++)
{
int id = str[i] - '0';
if(tree[root][id] == 0)
tree[root][id] = ++cnt;
root = tree[root][id];
sum[root]++;
}
val[root] = s;//记录root结点代表的字母
}
void query(char *str)
{
int len = strlen(str);
int root = 0;
for(int i = 0;i < len ; i++)
{
int id = str[i] - '0';
if(tree[root][id] != 0)
root = tree[root][id];
else
root=tree[root][!id];
if(val[root] == 'A' || val[root] == 'B' || val[root] == 'C' || val[root] == 'D')
{
printf("%c",val[root]);
root = 0;//如果找到了,就从根结点开始找下一个字母
}
}
printf("\n");
}
void init()
{
cnt = 0;
memset(tree,0,sizeof(tree));
memset(val,0,sizeof(val));
insert_word("101",'A');
insert_word("11",'B');
insert_word("0",'C');
insert_word("100",'D');
}
int main()
{
scanf("%s",str);
init();
query(str);
return 0;
}
补充
除了字符串问题,还可以解决这类问题:给你n个数,再给你x,让你从n个数里找出一个数m,使得m^x(m异或x,同0异1)最大或者最小。
直接从最高位开始,讲n个数的二进制每一位插入到字典树中,如果要使m^x最大,就每次走和 x 对应的二进制相反的边就行了。反之,就走和 x 对应的二进制相同的边就行了。
∙
\bullet
∙例题:
Zeus 和 Prometheus 做了一个游戏,Prometheus 给 Zeus 一个集合,集合中包含了N个正整数,随后 Prometheus 将向 Zeus 发起M次询问,每次询问中包含一个正整数 S ,之后 Zeus 需要在集合当中找出一个正整数 K ,使得 K 与 S 的异或结果最大。Prometheus 为了让 Zeus 看到人类的伟大,随即同意 Zeus 可以向人类求助。你能证明人类的智慧么?
Input
输入包含若干组测试数据,每组测试数据包含若干行。
输入的第一行是一个整数T(T < 10),表示共有T组数据。
每组数据的第一行输入两个正整数N,M(<1=N,M<=100000),接下来一行,包含N个正整数,代表 Zeus 的获得的集合,之后M行,每行一个正整数S,代表 Prometheus 询问的正整数。所有正整数均不超过2^32。
Output
对于每组数据,首先需要输出单独一行”Case #?:”,其中问号处应填入当前的数据组数,组数从1开始计算。
对于每个询问,输出一个正整数K,使得K与S异或值最大。
Sample Input
2
3 2
3 4 5
1
5
4 1
4 6 5 6
3
Sample Output
Case #1:
4
3
Case #2:
4
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <queue>
#include <vector>
#include <set>
#include <iostream>
#include <queue>
#include <string>
typedef long long LL;
using namespace std;
const int maxn = 1e5+10;
const int inf = 999999999;
LL tree[32*maxn][3],sum[32*maxn],cnt=0;
int n,m;
void insert_word(LL num){
int root = 0;
for(int i = 32 ; i >= 0 ; i--){
int id = (num >> i) & 1;
if(tree[root][id] == 0){
tree[root][id] = ++cnt;
}
root = tree[root][id];
}
sum[root] = num;//记录root结点代表的值
}
LL query(LL num)
{
int root = 0;
for(int i = 32 ; i >= 0 ; i--){
int id = (num >> i) & 1;
if(tree[root][!id] != 0){
root = tree[root][!id];
}
else {
root = tree[root][id];
}
}
return sum[root];
}
void init()
{
memset(tree,0,sizeof(tree));
memset(sum,0,sizeof(sum));
}
void in()
{
for(int i = 1 ; i <= n ; i++){
LL x;
scanf("%lld",&x);
insert_word(x);
}
}
int main()
{
int t;
scanf("%d",&t);
int flag=1;
while(t--){
scanf("%d%d",&n,&m);
init();
in();
printf("Case #%d:\n",flag++);
while(m--){
LL y;
scanf("%lld",&y);
printf("%lld\n",query(y));
}
}
return 0;
}