trie树(字典树)
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
Tire树就是用来快速储存和查找字符串集合的结构。
例如:我们构建一个Trie树 然后每到一个字符串的终点我们会做一个标记 当遍历到标记时就认为这个字符串存在于字典树中
看下图,我们给出字符串然后做出字典树
insert(插入操作)
p = 0
从根节点开始,我们开始遍历字符串,如果发现存在已有结点则p继续向下索引即: p = son[p][u]
,若不存在则我们建立结点,最后 cnt[p]++
,表示插入完毕,因此插入代码。
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]++;
}
(query)查询操作:
依然是 p = 0
从根节点开始,若查找失败则直接返回,否则返回出现次数 return 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];
}
总代码:
#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;
}
最大异或对
在给定的N个整数A1,A2……AN中选出两个进行xor(异或)运算,得到的结果最大是多少?
输入格式
第一行输入一个整数N。
第二行输入N个整数A1~AN。
输出格式
输出一个整数表示答案。
数据范围
1 ≤ N ≤ 10^5,
0 ≤ Ai < 2^31
输入样例:
3
1 2 3
输出样例:
3
这道题可知,trie树不仅仅可以存字符串,还可以存数字的二进制数。
首先考虑暴力的做法
int res = 0;
for(int i = 0; i < n; i++)
{
int t = a[i];
for(int j = 0; j < i; j++)
{
res = max(res, t ^ a[j]);
}
}
因为防止出现类似于1 3,3 1 这样重复比较的出现 我们第二重循环就直接遍历到i,因此我们发现,n个数选两个数两两组合则一共有种选法。
对于这道题的思路首先想:若要使得两数异或结果最大,则我们得从最高位开始考虑,假设:7与3,7与5
7:(111)2
3:(011)2
5:(101)2
从最高位开始看,7与3最高位异或得1,7与5最高位异或得0,则得出结论,很显然7与3异或得到结果更大。
因此我们维护一个最大值即结果,可以先把7得二进制插入trie树,现在trie树中只有7,因此3就与7异或得到一个数,更新结果,然后把3插入trie树,然后再拿5与trie树中的元素进行运算,现在得trie树有两个元素,看图:
然后,很明显左边分支最高位是0, 右边分支最高位是1,则我们肯定选择走左边分支,因为1与0异或是1,然后5的第二位0 与 3的第二位1异或得1依然最大,然后5的最低位1与3的最低位1异或得0,但是我们没有别的分支了,所以体现在程序里就是要先判断与它当前位置数不同的分支是否存在,如果存在则走与它不同的那个分支,否则只能走与它相同的分支。
insert操作(同上)
void insert(int x)
{
int p = 0;
for(int i = 30; i >= 0; i--)
{
int u = x >> i & 1;
if(!son[p][u]) son[p][u] = ++idx;
p = son[p][u];
}
}
查询操作
int query(int x)
{
int p = 0, res = 0; //返回异或后最大的数字结果
for(int i = 30; i >= 0; i--)
{
int u = x >> i & 1;
if(son[p][!u]) //如果发现与它不同的分支存在 则优先走与它不同的分支
{
p = son[p][!u];
res = res*2 + !u;
}
else
{
p = son[p][u];
res = res*2 + u;
}
}
return res;
}
总代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010, M = 31*N;
int n;
int a[N], son[M][2], idx;
void insert(int x)
{
int p = 0;
for(int i = 30; i >= 0; i--)
{
int u = x >> i & 1;
if(!son[p][u]) son[p][u] = ++idx;
p = son[p][u];
}
}
int query(int x)
{
int p = 0, res = 0; //返回异或后最大的数字结果
for(int i = 30; i >= 0; i--)
{
int u = x >> i & 1;
if(son[p][!u]) //如果发现与它不同的分支存在 则优先走与它不同的分支
{
p = son[p][!u];
res = res*2 + !u;
}
else
{
p = son[p][u];
res = res*2 + u;
}
}
return res;
}
int main()
{
scanf("%d", &n);
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
int res = 0;
for(int i = 0; i < n; i++) //可以插入一个查询一个,也可以先插入所有再查询
{
insert(a[i]);
int t = query(a[i]);
res = max(res, a[i] ^ t);
}
printf("%d\n", res);
system("pause");
return 0;
}
int res = 0;
for(int i = 0; i < n; i++)
{
int t = a[i];
for(int j = 0; j < i; j++)
{
res = max(res, t ^ a[j]);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端