trie树以及可持久化性质

样例题目https://www.acwing.com/problem/content/description/837/
图片出处https://www.acwing.com/solution/content/14695/

1.trie树的定义与性质

Trie树又称字典树、单词查找树。是一种能够高效存储和查找字符串集合的数据结构,可以精确的查找出字符串的出现个数。
Trie树一般有两个操作
1.向集合中插入一个字符串 x;
2.询问一个字符串在集合中出现了多少次。

具体实例如图所示,可以加强理解
在这里插入图片描述
当然我们还是需要使用数组模拟当前的trie树,这样可以准确的模拟字符串的出现次数。
具体如下图所示
https://www.acwing.com/solution/content/14695/
Trie树基本操作AC代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1000005;
int son[N][26],idx,cnt[N];
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]++;//将这个字符串加1
}
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;
    cin>>n;
    while (n -- )
    {
        char op[2];
        scanf("%s %s",&op,&str);
        if(*op=='I') 
        {
            insert(str);
        }
        else 
        {
            printf("%d\n",query(str));
        }
        
    }
}

2.经典例题

https://www.acwing.com/problem/content/145/
最大异或对
在给定的 N 个整数 A1,A2……AN 中选出两个进行 xor(异或)运算,得到的结果最大是多少?

输入格式
第一行输入一个整数 N。

第二行输入 N 个整数 A1~AN。

输出格式
输出一个整数表示答案。

数据范围
1≤N≤1e5,
0≤Ai<2的31次方
输入样例:
3
1 2 3
输出样例:
3
思路
因为数据很大,o(n*n)的时间复杂度肯定过不了,一般1e5的数据的时间复杂度是O(nlogn)。因为异或是二进制之间的运算,所以我们不难想到将十进制
转化为二进制,利用Trie来记录每个十进制数的二进制表示,因为题目要求是要求出两个数最大的异或值,要异或值越大则两个数二进制的高位不同(这里运用了贪心的思想)那么我们的思路已经非常的清晰了

#include <bits/stdc++.h>
using namespace std;
const int N = 1000015,M=N*31;
typedef long long ll;
int son[M][2];
int a[N];
int idx;
void insert(int x)//插入操作
{
    int p=0;
    for(int i=30;i>=0;i--)
    {
        int &s=son[p][x>>i&1];
        if(!s) s=++idx;
        p=s;
    }
    
}
int query(int x)//查询操作
{
    int p=0;
    int res=0;
    for(int i=30;i>=0;i--)
    {
        int s = x >> i & 1;
        if(son[p][!s])
        {
            res+=1<<i;
            p=son[p][!s];
        }
        else
        {
            p=son[p][s];
        
        }
    }
    return res;
}

int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        insert(a[i]);
    }
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        ans=max(ans,query(a[i]));
    }
    printf("%d",ans);
}

二.可持久化Trie树

1:可持久化Trie的用途

正常的Trie树可以解决字符串的一些问题,特殊的Trie树(比如0/1Trie)可以解决最大异或和的相关问题,但是如果每次的询问是针对区间的,Trie树就不好解决,因为你不能对每个区间都建一棵Trie树,那样空间就会爆炸,于是,我们的可持久化Trie就登场了。

2:可持久化Trie的构造

设trie[x][ch],表示从x号节点连向的字符为ch的点的编号(与普通Trie的含义相同),root[i]表示第i次插入的字符串的根节点,tot代表总节点数
可持久化Trie插入第i个字符串的构造流程如下:
1:首先新建第i个字符串的根节点,并定义两个变量p,q代表当前串的节点和上一个版本与之对应的节点,初始化p=root[i-1],q=root[i]
2:若当前字符串的下一个字符是ch,那么就让trie[q][ch]=++tot;
然后对于不是ch的字符CH,trie[q][CH]=trie[p][CH];
3:让p=trie[p][ch],q=trie[q][ch],并重复2,3,步直到建树完成

具体如下图所示
请添加图片描述

3:经典例题

https://www.acwing.com/problem/content/description/258/
最大异或对
给定一个非负整数序列 a,初始长度为 N。

有 M 个操作,有以下两种操作类型:

A x:添加操作,表示在序列末尾添加一个数 x,序列的长度 N 增大 1。
Q l r x:询问操作,你需要找到一个位置 p,满足 l≤p≤r,使得:a[p] xor a[p+1] xor … xor a[N] xor x 最大,输出这个最大值。
输入格式
第一行包含两个整数 N,M,含义如问题描述所示。

第二行包含 N 个非负整数,表示初始的序列 A。

接下来 M 行,每行描述一个操作,格式如题面所述。

输出格式
每个询问操作输出一个整数,表示询问的答案。

每个答案占一行。

数据范围
N,M≤3×105,0≤a[i]≤107。

输入样例:
5 5
2 6 4 3 6
A 1
Q 3 5 4
A 4
Q 5 7 0
Q 3 6 6
输出样例:
4
5
6
思路
这道题与上面的题目唯一不同的是就是在查询的时候添加的区间查询,对于区间两端[l,r],r这个边界我们可以利用异或前缀和来做限制。l的边界限制就比较麻烦。我们要创建一个max_id数组用来记录当前阶段的最大l边界,当max_id大于边界l时,我们就可以继续找异或最大的数字。

#include <bits/stdc++.h>
using namespace std;
const int N=6000010,M=6000010*25;
int tr[M][2],root[N],max_id[M];// tr表示trie树,root表示版本数 max_id表示区间最大下标
int n,m;
int s[N];//异或前缀和
int idx;
void insert(int i,int k,int p,int q)//插入操作 i表示根节点 k表示二进制位数 p表示上一个版本的Trie q表示这一个版本的Trie
{
    if(k<0)//如果已经遍历完了
    {
        max_id[q]=i;//记录当前的遍历的版本数
        return;
    }
    int v=s[i]>>k&1;
    if(p) tr[q][v^1]=tr[p][v^1];//如果上一个版本存在之间更新到这个版本
    tr[q][v]=++idx;//添加节点
    insert(i,k-1,tr[p][v],tr[q][v]);//递归
     max_id[q] = max(max_id[tr[q][0]], max_id[tr[q][1]]);//找到当前节点最大左区间
    
}
int query(int root,int c,int L)//查询操作 root表示根节点 c表示s[n] ^ x L表示左区间
{
    int p=root;//指针
    for(int i=23;i>=0;i--)
    {
        int v=c>>i&1;//提取二进制位数
        if(max_id[tr[p][v^1]]>=L) p=tr[p][v^1];// 如果P点最大区间大于左区间 更新最优的点
        else p=tr[p][v];
        
    }
    return c^s[max_id[p]];//s[p] ^ s[n] ^ x
}
int main()
{
    scanf("%d%d",&n,&m);
    root[0]=++idx;
    max_id[0]=-1;
    insert(0,23,0,root[0]);//建树
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d", &x);
        s[i]=s[i-1]^x;
        root[i]=++idx;
        insert(i,23,root[i-1],root[i]);
    }
    while (m -- )
    {
        char op[2];
       
        scanf("%s",&op);
        if(*op=='A')
        {
            int x;
            scanf("%d", &x);
            n++;
            s[n]=s[n-1]^x;
            root[n]=++idx;
            insert(n,23,root[n-1],root[n]);
        }
        else 
        {
            int l,r,x;
            scanf("%d%d%d", &l, &r,&x);
            printf("%d\n",query(root[r-1],s[n]^x,l-1));
        }
    }
    
    
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值