题意:
给定一个非负整数序列 {a},初始长度为n。
有 m 个操作,有以下两种操作类型:
A x:添加操作,表示在序列末尾添加一个数 x,序列的长度 n+1。
Q l r x:询问操作,你需要找到一个位置 pp,满足l≤p≤r,使得:a[p]⊕a[p+1]⊕…⊕a[N]⊕x 最大,输出最大是多少。
思路:
- 异或可以用前缀异或和维护,问题就变成了一个区间的数和x异或后的最大异或和
- 考虑朴素做法,每次都将区间的数插入到 t r i e trie trie树上,贪心找最大值;时间和空间都不允许;
- 可持久化数据结构可以查询历史区间的值,建立可持久化字典树,这样区间的问题就解决了;
- 还有几个小细节:
- 要事先插入0
- 空间的计算:3e5个数,18位,每个位有0/1两种可能,大于 3 e 5 ∗ 2 ∗ 18 3e5*2*18 3e5∗2∗18
代码:
const int maxn=6e5+10;
int n,m;///序列长度与询问个数
int root[maxn],idx;
int tr[maxn*25][2];
int sum[maxn];//前缀异或和
int max_id[maxn*25];//子树中下标的最大值
void insert(int i,int k,int p,int q){
//第i棵字典树 k为二进制拆分后的位
//p为上一个历史版本 q为当前版本
if(k<0){//拆分成二进制后所有位都已处理完
max_id[q]=i;return ;//记录子树下标的最大值,询问的l的条件
}
int v=sum[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]]);//类似线段树的pushup,用子节点的信息更新父节点
}
int query(int root,int val,int l){
///从r向前查询,用max_id判断是否>=l,与val值贪心处理
int p=root;
for(int i=23;i>=0;i--){
int v=val>>i&1;///这一位的值
if(max_id[tr[p][v^1]]>=l) p=tr[p][v^1];//贪心的考虑,能取对立先取对立
else p=tr[p][v];//不行取相同
}
return val^sum[max_id[p]];
}
int main(){
n=read,m=read;
root[0]=++idx;
max_id[0]=-1;
insert(0,23,0,root[0]);//处理和0异或的情况
rep(i,1,n){
int x=read;
sum[i]=sum[i-1]^x;
root[i]=++idx;
insert(i,23,root[i-1],root[i]);
}
char op[2];
while(m--){
cin>>op;
if(*op=='A'){
int x=read;
n++;
sum[n]=sum[n-1]^x;
root[n]=++idx;
insert(n,23,root[n-1],root[n]);
}
else{
int l=read,r=read,x=read;
printf("%d\n",query(root[r-1],sum[n]^x,l-1));
}
}
return 0;
}