可持久化字典树
可持久化字典树与可持久化线段树类似。
解决的问题都是类似于“有限制的前缀和”,或二维问题。
而可持久化字典树更多的是解决异或问题,即运用01字典树。
解决异或最大问题,贪心地在字典树上选择。
例题
用前缀异或和把最大的区间异或和转化成单点。
想要与 \(x\) 异或后最大,就从高到低 \(x\) 在二进制上的每一位,贪心的查找在这一位能不能取到与 \(x\) 相反的值。
查找:设合法的吗,与 \(x\) 异或值最大的数为\(ans\)。因为从高到低枚举每一位,所以 \(ans\) 的前缀已经知道,当前这一位加上后的\(ans'\) 也可以求出。在字典树上查一下,如果有 以 \(ans'\) 为前缀的数,那 \(ans'\)就是合法的。这里的查找实际上是检验贪心的取法会不会在后面出现问题。(取不到)
单点有 \(l,r\) 的限制,就用可持久化来解决。
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int MAXN=6e5+5;
int a[MAXN],s[MAXN];
int rt[MAXN];
int ch[MAXN*30][2],cnt[MAXN*30];
int tot;
void insert(int &u,int t,int x){//t:从右往左第t位
// printf("%d %d %d %d\n",u,t,x,id);
cnt[++tot]=cnt[u]+1;
if(t<0) {u=tot;return;}
ch[tot][0]=ch[u][0];
ch[tot][1]=ch[u][1];
u=tot;
int y=(x>>t)&1;
insert(ch[u][y],t-1,x);
}
int ask(int u,int v,int t,int x){
if(t<0) return 0;
int y=(x>>t)&1;
if(cnt[ch[v][!y]]>cnt[ch[u][!y]]) return (1<<t)+ask(ch[u][!y],ch[v][!y],t-1,x);
else return ask(ch[u][y],ch[v][y],t-1,x);
}
int main(){
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
scanf("%d%d",&n,&m);
insert(rt[0],25,0);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
s[i]=(s[i-1]^a[i]);
rt[i]=rt[i-1];
insert(rt[i],25,s[i]);
// printf("A\n");
}
insert(rt[0],25,0);
for(int i=1;i<=m;i++){
char op;
int l,r,x;
scanf("\n%c",&op);
if(op=='A'){
scanf("%d",&x);
n++;
s[n]=(s[n-1]^x);rt[n]=rt[n-1];
insert(rt[n],25,s[n]);
}else{
scanf("%d%d%d",&l,&r,&x);
l--,r--;
x^=s[n];
if(l==0) printf("%d\n",ask(0,rt[r],25,x));
else printf("%d\n",ask(rt[l-1],rt[r],25,x));
}
}
return 0;
}
这道题和模板题很像,但其实运用的只是字典树的思想。更重要的是这道题提供了一种新的01字典树的理解方式。
通常我们看字典树,一个节点表示的就是它到根节点所组成的数字/字符串。
但还有一种思路,一个节点,若它的前缀是 \(s\),到叶子节点的距离为 \(x\).那这个节点可以表示一个区间 \([s,s+(1<<x)-1]\).
本文来自博客园,作者:bwartist,转载请注明原文链接:https://www.cnblogs.com/bwartist/p/17700640.html

浙公网安备 33010602011771号