题意
给出一个序列,有两种操作。
一种是在序列后面加数,一种是询问一个区间内的所有后缀和一个给定数的最大异或和。
Sol
每次动态询问一个后缀和一个数的异或最值。
我们显然可以把每一个后缀的异或和看成是一个数。
但是由于每次插入是从后插入,如果我们维护后缀的化那么就要改很多东西了。
于是只能维护前缀,但怎么算呢。容斥一下,用全局的和异或一下就好了。
然后就是区间的异或最值问题了。
于是就写一个可持久化的trie树。
怎么写呢?
做法和主席树类似。记录每一棵trie的根节点。
每次更新的时候,不用进行修改的子节点就直接继承上上一棵trie的子节点。要修改的就新开节点就可以了。
询问也类似。我们在每一个点维护子树内有多少个数(终止节点,相同的也要算),然后求 到 某一个位为 的数的时候直接用 的 的 减掉 的 的 就可以了。
需要注意的是,由于我们这里维护的是前缀,时间戳应该要后移一位,具体来说就是先插入数再修改当前全局异或和。
代码:
#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<queue>
#include<algorithm>
#include<ctime>
#include<cstdio>
using namespace std;
inline int read()
{
int x=0;char ch=getchar();int t=1;
for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
return x*t;
}
int n,m;
const int N=3e5+10;
namespace trie{
int rt[N<<1];
const int Up=24;int cnt=0;
int next[2][50*N];
int count[50*N];
int Sum=0;
void insert(int &now,int x){
register int p1=now;now=++cnt;register int p2=cnt;
for(register int i=Up;~i;--i){
next[0][p2]=next[0][p1],next[1][p2]=next[1][p1],count[p2]=count[p1]+1;//继承
register int k=(x>>i)&1;
next[k][p2]=++cnt;p2=cnt;p1=next[k][p1];//新开
}
count[p2]=count[p1]+1;
}
void build(){
for(register int i=1;i<=n;++i){
register int x=read();
rt[i]=rt[i-1];insert(rt[i],Sum);
Sum^=x;
}
return;
}
inline int query(int l,int r,int x){
register int p1=rt[l],p2=rt[r];
register int ans=0;
for(register int i=Up;~i;--i){
ans<<=1;
register int k=(x>>i)&1;
register int sz=count[next[k^1][p2]]-count[next[k^1][p1]];
if(sz) ans|=1,p1=next[k^1][p1],p2=next[k^1][p2];
else p1=next[k][p1],p2=next[k][p2];
}
return ans;
}
void work(){
register int x,l,r;count[0]=next[0][0]=next[1][0]=0;
for(register int i=1;i<=m;++i){
char ch=getchar();
while(ch!='Q'&&ch!='A') ch=getchar();
if(ch=='A'){x=read();++n;rt[n]=rt[n-1];insert(rt[n],Sum);Sum^=x;}
else{
l=read();r=read();x=read();x^=Sum;
printf("%d\n",query(l-1,r,x));
}
}
}
}
int main()
{
n=read();m=read();
trie::build();trie::work();
return 0;
}