可持久化Trie
一、为什么要采用可持久化Trie
对于普通的字典树,将所有的节点插入字典树,那么这样基本只可以处理离线的问题。换言之,如果遇到涉及处理区间的问题时,那么普通的字典树就心有余而力不足了。
二、两者的差别以及可持久化Trie的构造
对于普通的字典树,它有且仅有一个根节点,那么我们只需要沿着这个根节点往下访问,即可获取到所有的元素。
而可持久化字典树,它有多个根节点,每一个新插入的元素都会新建一个根节点。
那如果我们直接暴力新建根,然后像建立Trie树一样新建节点,那么访问第 i i i 个根的时候只会访问到第 $i $ 个元素。但我们需要做的是什么呢,区间问题!那这样做肯定就不可行了,具体应该怎么做呢:
首先我们令第 i i i 个根的字典树,它可以查询到 1 ∼ i 1 \sim i 1∼i 的元素,也就是类似前缀和的思想,但如果暴力去插入,那么时间复杂度一定会超时,这个时候怎么办呢,考虑到可以重复利用之前的字典树,那么我们可以使得新根的字典树在新建节点连边时,并不一定要新建节点,我们可以使其指向之前根的字典树的某个节点,那么这样就可以重复利用之前的空间了,也就保证了时间复杂度的正确性。
需要注意的是,对于新增的元素,即第 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 1∼j 的元素了。
代码实现:
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[p−1]⊕x⊕xorsum
x o r s u m xorsum xorsum 代表数组所有元素的异或和。
那么我们只需要对其前缀异或和建立可持久化字典树,然后正常跑字典树的异或最大值即可,但这样出现的问题就是只可以得到 1 ∼ r 1\sim r 1∼r 的异或最大值,无法得到 l ∼ r l \sim r l∼r 的,那具体怎么做呢,我们可以维护一个每个节点的前缀出现次数,那么往下跳的过程中,如果 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[l−1][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;
}