Trie树+例题

109 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情

概念

  1. 用来快速存储和查找字符串集合的数据结构
  2. 存储:一般来说,都会在每个单词的结尾字符打个标记,表示以这个字符结尾是有一个单词的image.png
  3. 查找:可以查找某个字符串在整个集合中是否出现过,并且出现过多少次

题目

www.acwing.com/problem/con… image.png

分析

暴力做法image.png

  1. 两重循环
    1. 第一重循环,枚举第一个数
    2. 第二重循环枚举第二个数
  2. 含义:选择了ai,那么后面就从a[0]~[ai-1]中选一个数跟a[i]进行异或,找出异或后的最大值

优化

image.png

  1. 假设有一个数a[i],题目给出最多有31位二进制数,那么每次在字典树里面最好就找跟a[i]的每一位上都不同的数,一直找到叶子结点,此时就是跟a[i]进行异或求出来的值最大的数
  2. 可以一遍插入一遍查找,具体步骤参照例子部分的演示

本题由感而发

Trie树不仅可以存储字符串,也可以存二进制数

例子

image.png

  1. 先将5插进去,然后查找,发现只有自己,那么此时最大值是0
  2. 对于6先查询Tire树,使得最好,走的路径都跟110的每一位上的树尽量不同,但是此时Tire树里面只有5,所以将6和5进行异或,得到011,也就是异或值是3,然后将110插入到Tire树中
  3. 对于3,步骤跟2.一样,发现跟5进行异或的值会最大,得到异或后的值110,然后将3插入tire树中
  4. 依次类推,知道遍历到最后一个数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个节点(不算重复节点)