可持久化字典树

可持久化Trie

一、为什么要采用可持久化Trie

对于普通的字典树,将所有的节点插入字典树,那么这样基本只可以处理离线的问题。换言之,如果遇到涉及处理区间的问题时,那么普通的字典树就心有余而力不足了。

二、两者的差别以及可持久化Trie的构造

对于普通的字典树,它有且仅有一个根节点,那么我们只需要沿着这个根节点往下访问,即可获取到所有的元素。

而可持久化字典树,它有多个根节点,每一个新插入的元素都会新建一个根节点。

那如果我们直接暴力新建根,然后像建立Trie树一样新建节点,那么访问第 i i i 个根的时候只会访问到第 $i $ 个元素。但我们需要做的是什么呢,区间问题!那这样做肯定就不可行了,具体应该怎么做呢:

首先我们令第 i i i 个根的字典树,它可以查询到 1 ∼ i 1 \sim i 1i 的元素,也就是类似前缀和的思想,但如果暴力去插入,那么时间复杂度一定会超时,这个时候怎么办呢,考虑到可以重复利用之前的字典树,那么我们可以使得新根的字典树在新建节点连边时,并不一定要新建节点,我们可以使其指向之前根的字典树的某个节点,那么这样就可以重复利用之前的空间了,也就保证了时间复杂度的正确性。

需要注意的是,对于新增的元素,即第 i i i 个元素,它对应的每一个节点必须新建,不可以重复利用之前的节点,否则在进行连边建点时,会影响之前根的字典树,例如:第 i i i 个元素和第 j j j 个元素都是"abbc",那 i i i 根节点连边可以直接连 j j j 的吗,显然不可行,如果连了,又在 i i i 上连接了其他节点,那对应的,从 j j j 这个根同样可以访问到 i i i 连接的点,那么就不一定满足访问 j j j 的根会得到 1 ∼ j 1\sim j 1j 的元素了。

代码实现:
void insert(string s,int pos){
    now=++tot;//新建根节点
    root[pos]=now;//第pos个位置的根节点
    int tmp=now;
    for(auto i:s){
        i-='a';
        for(int j=0;j<26;j++){
            if(j!=i){
                tr[now][j]=tr[pre][j];
                //不属于新建值的节点,那么直接利用之前的字典树
            }
        }
        tr[now][i]=++tot;//自身的需要新建
        now=tr[now][i];
        pre=tr[pre][i];
        cnt[now]=cnt[pre]+1;//记录某个点在1~i中出现的次数
    }
    pre=tmp;//更新上一个根节点
}

三、例题

P4735 最大异或和

思路:我们可以将其转化为前缀异或。

a [ p ] ⊕ a [ p + 1 ] ⊕ . . . ⊕ a [ N ] ⊕ x = a [ 1 ] ⊕ a [ 2 ] ⊕ ⋯ ⊕ a [ p − 1 ] ⊕ x ⊕ x o r s u m a[p] \oplus a[p+1] \oplus ... \oplus a[N] \oplus x=a[1] \oplus a[2] \oplus \cdots \oplus a[p-1] \oplus x \oplus xorsum a[p]a[p+1]...a[N]x=a[1]a[2]a[p1]xxorsum

x o r s u m xorsum xorsum 代表数组所有元素的异或和。

那么我们只需要对其前缀异或和建立可持久化字典树,然后正常跑字典树的异或最大值即可,但这样出现的问题就是只可以得到 1 ∼ r 1\sim r 1r 的异或最大值,无法得到 l ∼ r l \sim r lr 的,那具体怎么做呢,我们可以维护一个每个节点的前缀出现次数,那么往下跳的过程中,如果 c n t [ t r [ r ] [ c ] ] − c n t [ t r [ l − 1 ] [ c ] ] > 0 cnt[tr[r][c]]-cnt[tr[l-1][c]] \gt 0 cnt[tr[r][c]]cnt[tr[l1][c]]>0,那说明就可以跳到 c c c 这个节点。

注意:少用 vector<vector<int>> 开字典树,血的教训。。。会爆内存,因为vector本身会多开2倍左右。

代码实现:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<iomanip>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<stack>
//#include<random>
//#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
// // int tr[1000100*60][2];
// // int cnt[1000100*60];
// // int root[1000100];
// array<int,2>tr[1000100*60];
// vector<int>cnt(1e6*60+10,0),root(1e6+10,0);
struct ties{
    vector<array<int,2>>tr;
    vector<int>cnt,root;
    int tot,now,pre,m;
    ties(int n){
        tr=vector<array<int,2>>(n*64+5);
        cnt=vector<int>(n*64+10,0);
        root=vector<int>(n+10,0);
        tot=now=pre=0;
        m=0;
    }
    void insert(int x){
        now=++tot;
        root[++m]=now;
        int tmp=now;
        for(int i=31;i>=0;i--){
            int num=(x>>i&1);
            tr[now][num^1]=tr[pre][num^1];
            tr[now][num]=++tot;
            now=tr[now][num];
            pre=tr[pre][num];
            cnt[now]=cnt[pre]+1;
        }
        pre=tmp;
    }
    // int find(int x){
    //     int tmp=pre;
    //     for(int i=31;i>=0;i--){
    //         tmp=tr[tmp][x>>i&1];
    //     }
    //     return tmp!=0;
    // }
    int trmax(int l,int r,int x){
        int ans=0;
        l--;
        l=root[l],r=root[r];
        for(int i=31;i>=0;i--){
            int num=(x>>i&1);
            if(cnt[tr[r][num^1]]-cnt[tr[l][num^1]]>0){
                r=tr[r][num^1];
                l=tr[l][num^1];
                ans^=(1<<i);
            }
            else {
                r=tr[r][num];
                l=tr[l][num];
            }
        }
        return ans;
    }
};
void solve(){
    int n,m;
    cin>>n>>m;
    ties T(n+m);
    int prexor=0;
    T.insert(0);
    for(int i=1;i<=n;i++){
        int x;cin>>x;
        prexor^=x;
        T.insert(prexor);
    }
    while(m--){
        char c;
        cin>>c;
        if(c=='A'){
            int x;cin>>x;
            prexor^=x;
            T.insert(prexor);
        }
        else {
            int l,r,x;cin>>l>>r>>x;
            x^=prexor;
            cout<<T.trmax(l,r,x)<<'\n';
        }
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int t=1;
    // cin>>t;
    while(t--)solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值