Trie字典树

文章首发于:My Blog 欢迎大佬们前来逛逛

字典树(Trie树)

一:概念

字典树是一种树形结构,用于存储一组字符串,支持快速的字符串查找和前缀匹配

字典树的本质是利用字符串之间的公共前缀,将具有相同前缀的字符串合并在一起,从而实现高效的字符串操作。

数据结构 字典树是一棵多叉树,每个节点包含若干个指向子节点的指针,每个节点代表一个字符。根节点代表空字符串,每个非根节点代表一个非空前缀。如果某个节点代表的字符串在字典树中存在,则该节点为终止节点;否则,该节点不是终止节点。

三:操作

字典树支持以下操作:

1. **插入操作** 将一个字符串插入字典树中,需要从根节点开始遍历字符串的每个字符,如果当前字符对应的节点存在,则继续向下遍历;否则,新建一个节点,并将其插入到当前节点的子节点中。最后,将字符串的最后一个字符对应的节点标记为终止节点。
2. **查找操作** 查找一个字符串是否在字典树中,需要从根节点开始遍历字符串的每个字符,如果当前字符对应的节点存在,则继续向下遍历;否则,说明该字符串不存在于字典树中。
3. **前缀匹配操作** 在字典树中查找一个字符串的前缀匹配,需要从根节点开始遍历字符串的每个字符,如果当前字符对应的节点存在,则继续向下遍历;否则,说明该字符串的前缀不存在于字典树中

四:应用场景

字典树的应用场景非常广泛,包括但不限于以下几个方面:

  1. 字符串查找:在一组字符串中查找指定的字符串是否存在,可以使用字典树来实现。

  2. 词频统计:将一组文本分词,并统计每个词出现的频率,可以使用字典树来实现。

  3. 自动补全:在用户输入过程中,根据已输入的前缀,自动补全可能的后缀,可以使用字典树来实现。

  4. 模式匹配:在一组文本中查找某个模式出现的位置,可以使用字典树来实现。

143. 最大异或对

143. 最大异或对 - AcWing题库

题目要求:给你一些数字,让你找出其中异或和最大的一对数字

示例:

5
2 4 3 6 5

其中 2和5 或者 4和3异或和之后为7,都是最大值,因此结果为7.


如果我们知道一个数字比如 4,则此二进制为 100,则我们尽量需要找 011的,因为只有这样我们的异或和是 111,是一个在这两个数字异或范围内的最大值。

同理,对于其他的数字也是,我们只要找到与num每一位二进制数尽量相反的数字即可

因此对于所有的数字都是如此,最大的数字可以到达 2^31,所以我们在对Trie树进行操作的时候要:

for (int i=30;~i;i--){
	...
}

其中~i表示 i>=0,两者等价

过程如下:

  1. 对于每一个数字进行插入到字典树中。
  2. 对于每一个数字在Trie中查询其最大异或值。
  3. 最后输出最大值。

Trie图如下所示:

因此我们插入之后形成的Trie树如上图所示。

对于查询每一对之间的最大异或值,我们首先要尽量找到每一位都与当前数字相反的Trie分支;如果没有与当前数字相反的分支,则我们只能选择与当前数字相同的分支

  • 如何获取当前数字的每一个位数?

    for (int i=30;~i;i--){
    	int u=x>>i&1;
    }
    
  • 如何以当前数字的二进制位的数值创建Trie分支?

if (!tree[p][u]){
	tree[p][u]=++idx;//idx就是每一个节点
}
  • 查询时如何当前二进制位有没有相同的分支?
if (tree[p][!u]){ //u表示当前的二进制位
    //!u 表示如果存在相反的分支
	ans+=1<<i; // 第i位为1,转换为十进制
	p=tree[p][!u];
}
else{
    //!u 表示不存在相反的,则只存在相同的。
	p=tree[p][u];
}

#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
using namespace std;
const int N=1e5+10;
int nums[N];
int tree[N*31][2],idx;
int n,m;
void insert(int x){
    int p=0;
    for (int i=30;~i;i--){ //~i == i>=0
        int u=x>>i&1; //获得二进制中第i位的值
        if (!tree[p][u]){
            tree[p][u]=++idx;
        }
        p=tree[p][u];
    }
}
int query(int x){
    int ans=0;
    int p=0;
    for (int i=30;~i;i--){
        int u=x>>i&1;
        if (tree[p][!u]){ //与当前数字不相等的分支是存在的
            //往不同的分支走
            ans+=1<<i;
            p=tree[p][!u];
        }
        else{//不存在相等的分支
            //往相同的分支走
            p=tree[p][u];
        }
    }
    return ans;
}
signed main(){
    cin>>n;
    for (int i=1;i<=n;i++){
        cin>>nums[i];
        insert(nums[i]);//插入字典树
    }
    int ans=0;
    for (int i=1;i<=n;i++){
        ans=max(ans,query(nums[i]));
    }
    cout<<ans;
    return 0;
}

3485. 最大异或和

3485. 最大异或和 - AcWing题库


题目要求:找到序列中长度不超过m的子序列的最大异或值和。

最大异或对与这道题相似,只不过这道题求得是不超过m的子序列的最大异或和。

首先我们需要知道几个性质:

  • 前缀和不仅可以维护和,还可以维护异或值的和
sum[i]=sum[i-1]^x

因此 sum[i]-sum[j-1] 表示 【j,i】范围内的数字的异或和

因此对于一个长度不超过M的子序列来说,如果我们固定住右端点 i ,则我们就可以枚举左端点 j。

通过sum[i]-sum[j-1],如果sum[i]是固定的,则 j 就可以通过在 [ i-M+1,i ] 中找到最小的异或值的和,即寻找sum[j-1]的最小值

再通过sum[i] -sum[j-1]得到长度不超过m的子区间[j,r]的异或和的最大值

因此我们就需要维护一个窗口,同时需要完成以下三种操作:

  • 往区间中插入元素(窗口右端点)。
  • 往区间中删除元素(窗口左端点的前一个位置)。
  • 找到与sum[i]异或值最大的数。

通过query函数便可以找到与 sum[i]异或值和最大的结果。

ans=max(ans,query(sum[i])); //表示查询以i为右端点的异或值的前缀和的 最大值

最后ans就是我们的答案。

其中我们需要注意由于需要维护一个长度为M的窗口,因此当我们需要在移动的时候删除窗口左端点元素,与添加一个元素到右端点。

  • 何时删除元素?

由于 j 属于 [i-M+1,i]中, 因此 j-1 属于 [i-M,i-1]中,因为对于前缀和我们需要的是 sum[j-1]。因此 i - M表示的是长度为M窗口需要求前缀和的最左端位置,因此窗口往右移动的时候,我们需要删除最左端位置的前一个位置

if (i-m-1>=0) insert(sum[i-m-1],-1); //-1表示删除
  • 何时插入元素?

枚举新的右端点 i ,直接插入即可:

insert(sum[i],1);

要时刻记住:我们都是对 某个区间异或和的前缀和 进行操作的,而不是原数组,不要搞混了。

#include <iostream>
#include <cstring>
#include <string>
using namespace std;
const int N=1e5+10;
int n,m,sum[N],tree[N*32][2],idx,cnt[N*32];
void insert(int x,int v){
    int p=0;
    for (int i=30;i>=0;i--){
        int u=x>>i&1;
        if (!tree[p][u]){
            tree[p][u]=++idx;
        }
        p=tree[p][u];
        cnt[p]+=v;
    }
}
int query(int x){
    int ans=0;//ans保存的是 x其他数字异或的最大结果
    int p=0;
    for (int i=30;i>=0;i--){
        int u=x>>i&1;
        if (cnt[tree[p][!u]]){//由于当前寻找的范围不是整个tree,而是当前可异或节点的出现次数
            //相反分支存在:+1
            ans=ans*2+1;
            p=tree[p][!u];
        }
        else{
            //相同分支:+0
            ans=ans*2;
            p=tree[p][u];
        }
    }
    return ans;
}
int main(){
    cin>>n>>m;
    for (int i=1;i<=n;i++){
        cin>>sum[i];
        sum[i]=sum[i-1]^sum[i];
    }
    int ans=0;
    insert(0,1);
    for (int i=1;i<=n;i++){
        if (i-m-1>=0) insert(sum[i-m-1],-1);
        ans=max(ans,query(sum[i]));
        insert(sum[i],1);
    }
    cout<<ans;
    return 0;
}

posted @ 2023-03-29 12:41  hugeYlh  阅读(12)  评论(0编辑  收藏  举报