一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情。
概念
- 用来快速存储和查找字符串集合的数据结构
- 存储:一般来说,都会在每个单词的结尾字符打个标记,表示以这个字符结尾是有一个单词的
- 查找:可以查找某个字符串在整个集合中是否出现过,并且出现过多少次
题目
分析
暴力做法
- 两重循环
- 第一重循环,枚举第一个数
- 第二重循环枚举第二个数
- 含义:选择了ai,那么后面就从a[0]~[ai-1]中选一个数跟a[i]进行异或,找出异或后的最大值
优化
- 假设有一个数a[i],题目给出最多有31位二进制数,那么每次在字典树里面最好就找跟a[i]的每一位上都不同的数,一直找到叶子结点,此时就是跟a[i]进行异或求出来的值最大的数
- 可以一遍插入一遍查找,具体步骤参照例子部分的演示
本题由感而发
Trie树不仅可以存储字符串,也可以存二进制数
例子
- 先将5插进去,然后查找,发现只有自己,那么此时最大值是0
- 对于6先查询Tire树,使得最好,走的路径都跟110的每一位上的树尽量不同,但是此时Tire树里面只有5,所以将6和5进行异或,得到011,也就是异或值是3,然后将110插入到Tire树中
- 对于3,步骤跟2.一样,发现跟5进行异或的值会最大,得到异或后的值110,然后将3插入tire树中
- 依次类推,知道遍历到最后一个数7
代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010, M = 31 * N;
int n;
int a[N];
int son[M][2], idx;
// 插入一个数
void insert(int x)
{
int p = 0; // 先让p等于根节点
for (int i = 30; i >= 0; i--) // 从最高位开始
{
int u = x >> i & 1;// 每次取出来当前的这一位
if (!son[p][u]) son[p][u] = ++idx; // 如果不存在,就创建出来
p = son[p][u]; // 移动p到当前位置
}
}
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]; // p走到另外一个方向
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]);
// 先插入再查询为了保证,每次查询的时候,Tire树里面都有数,可以少写一些判断判断条件
int t = query(a[i]); // 查询一个数
res = max(res, a[i] ^ t); // 更新一下结果
}
printf("%d\n", res); // 输出答案
return 0;
}
为什么是31 * N呢,以及son[M][2]呢?
因为m数代表二维数组有多少行,即有多少(idx)个节点。每一个数都是以31位的二进制数存进去的(高位为0也会算31位),所以不算重复一个数需要31个节点去存,一共100010个数,所以一共就有3100000个节点(不算重复节点)