可持久化数据结构
标 * 是推荐做的题目。
1. 可持久化线段树(**树)
zhu xi 竟然被和谐了。
1.1. 算法简介
注意到单点修改线段树时,最多只会遍历到 \(\log n\) 个区间,因此对于这 \(\log n\) 个区间新建节点,其余的左儿子或右儿子继承原来的节点即可做到持久化。
1.2. Trick:标记永久化
对于区间修改的题目,虽然可以通过下推标记维护信息,但是每次下推标记时,如果没有左右儿子,那么就要新建节点,空间开销过大。
不妨不下推标记,仅在查询时计算标记的贡献。常见于区间加 + 查询区间和/最值中,因为加法可以累积,即查询时额外储存从根节点到当前区间表示节点的标记和,再加上该区间所存储的值就是当前值。
如果是区间赋值,那么可以在标记中额外维护时间戳,查询时找到时间戳最大的那一次修改的权值即为当前值。
具体例题可以看 SP11470 TTM - To the moon。
1.3. 应用:静态区间第 k 小
首先将权值离散化(当然不离散化直接动态开点也可以,不过常数会稍大一些)。
运用前缀和的思想,按照下标顺序依次将每个值加入维护出现次数的线段树,并运用**树维护每次修改后线段树的形态。这样,将第 \(r\) 棵线段树的所有值减去第 \(l-1\) 棵线段树上对应的值,即为 \(a_{l\sim r}\) 中所有数所建出的线段树,每个节点的值为 \(a_{l\sim r}\) 中有多少个数的值落在该节点所表示的区间中。
显然我们不能完全建出 \(a_{l\sim r}\) 表示的线段树,不过其实并不需要这么做。运用线段树二分的思想,如果当前区间的左儿子的值(记为 \(v\),显然 \(v=val_{ls_y}-val_{ls_x}\),其中 \(x,y\) 分别是第 \(l-1\) 和第 \(r\) 棵线段树当前节点的编号)不小于 \(k\),也即有不小于 \(k\) 个 \(a_{l\sim r}\) 中的数的值落在该节点左儿子所表示的区间中。此时向左儿子递归查询即可。否则将 \(k\) 减去 \(v\),再向右儿子递归查询。
时间复杂度 \(n\log n\sim n\log c\),其中 \(c\) 是值域。
1.4. **树的带修扩展
**树带修了那还能叫**树么?
详见 线段树的高级用法 3. BIT 套权值线段树。
1.5. 例题
*I. P2839 [国家集训队]middle
非常 nb 的一道题目!
显然答案满足可二分性,将其转化为 “中位数是否 \(\geq x\)”:所有 \(<x\) 的数视作 \(-1\),\(\geq x\) 的数视作 \(1\),则题目转化为求端点分别在 \([a,b]\),\([c,d]\) 的区间之和最大值是否不小于 \(0\),可以进一步转化为 \([a,b)\) 的最大可空后缀和 + \([b,c]\) 的和 + \((c,d]\) 的最大可空前缀和,这个用线段树可以轻松维护。
但是不能对每个值都建线段树:注意到如果按权值从小到大添加数,那么每次修改最多改变一个位置的值。于是用**树即可。时间复杂度 \(n\log^2 n\)。
#include<bits/stdc++.h>
using namespace std;
const int N=2e4+5;
int n,q,las,b[N],c[4];
struct node{
int id,val;
bool operator < (const node &v) const {
return val<v.val;
}
}a[N];
struct data{
int s,pre,suf;
friend data operator + (data a,data b){
data c; c.s=a.s+b.s;
c.pre=max(a.pre,a.s+b.pre);
c.suf=max(b.suf,b.s+a.suf);
return c;
}
}val[N<<5];
int nd,R[N],ls[N<<5],rs[N<<5];
void build(int l,int r,int &x){
x=++nd,val[x]={r-l+1,r-l+1,r-l+1};
if(l==r)return;
int m=l+r>>1;
build(l,m,ls[x]),build(m+1,r,rs[x]);
} void modify(int pr,int &x,int l,int r,int p){
x=++nd,ls[x]=ls[pr],rs[x]=rs[pr];
if(l==r)return val[x]={-1,0,0},void();
int m=l+r>>1;
if(p<=m)modify(ls[pr],ls[x],l,m,p);
else modify(rs[pr],rs[x],m+1,r,p);
val[x]=val[ls[x]]+val[rs[x]];
} data query(int l,int r,int ql,int qr,int x){
if(ql<=l&&r<=qr)return val[x];
int m=l+r>>1; data ans={0,0,0};
if(ql<=m)ans=query(l,m,ql,qr,ls[x]);
if(m<qr)ans=ans+query(m+1,r,ql,qr,rs[x]);
return ans;
}
int main(){
cin>>n,build(1,n,R[0]);
for(int i=1;i<=n;i++)cin>>a[i].val,b[i]=a[i].val,a[i].id=i;
sort(a+1,a+n+1),sort(b+1,b+n+1);
for(int i=1;i<n;i++)modify(R[i-1],R[i],1,n,a[i].id);
cin>>q; while(q--){
for(int i=0;i<4;i++)cin>>c[i],c[i]=(c[i]+las)%n+1;
int l=0,r=n-1; sort(c,c+4);
while(l<r){
int m=(l+r>>1)+1;
data lp=query(1,n,c[0],c[1]-1,R[m]);
data mid=query(1,n,c[1],c[2],R[m]);
data rp=query(1,n,c[2]+1,c[3],R[m]);
if(lp.suf+mid.s+rp.pre>=0)l=m;
else r=m-1;
} cout<<(las=b[l+1])<<endl;
}
return 0;
}
II. P3168 [CQOI2015]任务查询系统
将一段区间拆成单点加减,然后就是裸的**树了。
时间复杂度 \(n\log n\)(假设 \(n,m\) 同阶)。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+5;
int m,n,cnt,va[N];
struct node{
int t,p,tp;
bool operator < (const node &v) const {
return t<v.t;
}
}c[N<<1];
int nd,R[N],ls[N<<6],rs[N<<6];
ll val[N<<6],sum[N<<6];
void modify(int pre,int &x,int l,int r,int p,int v){
val[x=++nd]=val[pre]+v,sum[x]=sum[pre]+va[p]*v;
if(l==r)return;
int m=l+r>>1;
if(p<=m)modify(ls[pre],ls[x],l,m,p,v),rs[x]=rs[pre];
else modify(rs[pre],rs[x],m+1,r,p,v),ls[x]=ls[pre];
} ll query(int l,int r,int k,int x){
if(l==r)return min((ll)k,val[x])*va[l];
ll m=l+r>>1;
if(val[ls[x]]>=k)return query(l,m,k,ls[x]);
return sum[ls[x]]+query(m+1,r,k-val[ls[x]],rs[x]);
}
int main(){
cin>>m>>n;
for(int i=1,s,e,p;i<=m;i++)
cin>>s>>e>>p,va[i]=p,c[++cnt]={s,p,1},c[++cnt]={e+1,p,-1};
sort(va+1,va+m+1),sort(c+1,c+cnt+1);
for(int i=1,p=1;i<=n;i++){
bool tag=0;
while(p<=cnt&&c[p].t<=i){
int id=lower_bound(va+1,va+m+1,c[p].p)-va;
modify(tag?R[i]:R[i-1],R[i],1,m,id,c[p++].tp),tag=1;
} if(!tag)R[i]=R[i-1];
}
for(ll i=1,x,k,a,b,c,pre=1;i<=n;i++){
cin>>x>>a>>b>>c,k=(a*pre+b)%c+1;
cout<<(pre=query(1,m,k,R[x]))<<endl;
}
return 0;
}
*III. P3293 [SCOI2016]美味
类似 01Trie 从高位往低位贪心:设已经选择的所有位的数字为 \(d\),如果 \(b\) 的这一位(\(i\))是 \(0\),那么看 \(a_{l\sim r}\) 中是否有 \([d+2^i-x,d+2^{i+1}-x)\),反之亦然,**树即可。
时间复杂度 \(\mathcal{O}(n\log^2 n)\)。
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5;
const int lim=1e5-1;
int n,m,node,R[N],ls[N<<5],rs[N<<5],val[N<<5];
void modify(int pre,int &x,int l,int r,int p){
val[x=++node]=val[pre]+1,ls[x]=ls[pre],rs[x]=rs[pre];
if(l==r)return;
int m=l+r>>1;
if(p<=m)modify(ls[pre],ls[x],l,m,p);
else modify(rs[pre],rs[x],m+1,r,p);
}
int query(int l,int r,int ql,int qr,int x,int y){
if(ql>qr)return 0;
if(ql<=l&&r<=qr)return val[y]-val[x];
int m=l+r>>1,ans=0;
if(ql<=m)ans=query(l,m,ql,qr,ls[x],ls[y]);
if(m<qr)ans+=query(m+1,r,ql,qr,rs[x],rs[y]);
return ans;
}
int main(){
cin>>n>>m;
for(int i=1,a;i<=n;i++)cin>>a,modify(R[i-1],R[i],0,lim,a);
for(int i=1,b,x,l,r,d=0;i<=m;i++,d=0){
cin>>b>>x>>l>>r;
for(int i=17;~i;i--){
int bit=(b>>i)&1,c=(bit^1)<<i;
int ql=max(0,d+c-x),qr=min(lim,d+c+(1<<i)-1-x);
if(query(0,lim,ql,qr,R[l-1],R[r]))d+=(bit^1)<<i;
else d+=bit<<i;
}
cout<<(b^d)<<endl;
}
return 0;
}
2. 可持久化(撤销)并查集
2.1. 算法简介
基于可持久化数组。
因为每次 merge
两个集合时,只需要修改其中一个代表元指向的父节点,于是可以用可持久化数组维护回溯各个版本的并查集。
注意既要维护 \(fa\) 又要维护 \(size/dep\),因为不能用路径压缩(空间会炸),只能按秩合并。
如果只需要撤销,那么用栈维护修改再回溯即可。
2.2. 应用
可持久化并查集可以用来维护边权有上界或下界时,所有合法的边所形成的连通块。具体地,将边按边权从小到大的顺序加入并查集,每加入一条边就记录一个版本的 \(fa\) 数组。这样每次查询代表元的时间复杂度是 \(\log^2 n\) 的(查 \(fa\) \(\log\) 加上深度的 \(\log\))。
如果给出 \((u,v,w)\) 询问能否不经过权值不大于 \(w\) 的边从 \(u\) 走到 \(v\),那么在加入最大的边权不大于 \(w\) 的边之后的那一个版本的并查集中查询 \(u,v\) 代表元是否相同即可。
2.3. 例题
*I. P4768 [NOI2018] 归程
首先 dijkstra 求出每个点到节点 \(1\) 的最短路长度 \(dis\)。
如果离线那么将询问和边按照海拔从高到低排序,并查集维护连通块及其内部所有点的 \(dis\) 最小值。
强制在线可持久化一下即可,时间复杂度 \(\mathcal{O}(n\log^2 n)\)。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pb push_back
#define pii pair <int,int>
#define fi first
#define se second
#define mem(x,v) memset(x,v,sizeof(x))
int read(){
int x=0; char s=getchar();
while(!isdigit(s))s=getchar();
while(isdigit(s))x=(x<<1)+(x<<3)+s-'0',s=getchar();
return x;
}
const int N=2e5+5;
const int M=4e5+5;
int n,m,q,k,s,h[M];
vector <pii> g[N];
struct edge{
int u,v,a;
bool operator < (const edge &v) const {
return a>v.a;
}
}e[M];
priority_queue <pii,vector<pii>,greater<pii> >p;
ll dis[N];
void dij(){
mem(dis,0x7f),p.push({0,1}),dis[1]=0;
while(!p.empty()){
pii t=p.top(); p.pop();
int id=t.se,d=t.fi;
if(dis[id]<d)continue;
for(int i=0;i<g[id].size();i++){
int to=g[id][i].fi,nd=d+g[id][i].se;
if(dis[to]>nd)dis[to]=nd,p.push({nd,to});
}
}
}
int node,R[M],ls[N<<6],rs[N<<6],val[N<<6],ans[N<<6];
void build(int l,int r,int &x){
x=++node;
if(l==r)return val[x]=l,ans[x]=dis[l],void();
int m=l+r>>1;
build(l,m,ls[x]),build(m+1,r,rs[x]);
}
void modify(int pre,int &x,int l,int r,int p,int v,int tp){
x=++node,ls[x]=ls[pre],rs[x]=rs[pre],val[x]=val[pre],ans[x]=ans[pre];
if(l==r)return tp?ans[x]=v:val[x]=v,void();
int m=l+r>>1;
if(p<=m)modify(ls[pre],ls[x],l,m,p,v,tp);
else modify(rs[pre],rs[x],m+1,r,p,v,tp);
}
int query(int l,int r,int x,int p,int tp){
if(l==r)return tp?ans[x]:val[x];
int m=l+r>>1;
if(p<=m)return query(l,m,ls[x],p,tp);
return query(m+1,r,rs[x],p,tp);
}
int dfs(int id,int x){
int f=query(1,n,R[id],x,0);
return f==x?x:dfs(id,f);
}
int f[N],sz[N];
int find(int x){return f[x]==x?x:find(f[x]);}
void merge(int i,int u,int v){
u=find(u),v=find(v);
if(u==v)return R[i]=R[i-1],void();
if(sz[u]<sz[v])swap(u,v);
sz[u]+=sz[v],f[v]=u;
modify(R[i-1],R[i],1,n,v,u,0);
modify(R[i],R[i],1,n,u,dis[u]=min(dis[u],dis[v]),1);
}
int main(){
int t; cin>>t;
while(t--){
cin>>n>>m;
for(int i=1;i<=m;i++){
int u=read(),v=read(),l=read(),a=read();
g[u].pb({v,l}),g[v].pb({u,l}),e[i]={u,v,a},h[i]=a;
} sort(e+1,e+m+1),sort(h+1,h+m+1),dij();
build(1,n,R[0]);
for(int i=1;i<=n;i++)f[i]=i,sz[i]=1;
for(int i=1;i<=m;i++)merge(i,e[i].u,e[i].v);
cin>>q>>k>>s;
for(int i=1,las=0;i<=q;i++){
int x=(read()+(ll)k*las-1)%n+1,p=(read()+(ll)k*las)%(s+1);
int id=m-(upper_bound(h+1,h+m+1,p)-h-1);
printf("%d\n",las=query(1,n,R[id],dfs(id,x),1));
}
for(int i=1;i<=n;i++)g[i].clear();
node=0,mem(ls,0),mem(rs,0);
}
return 0;
}
*II. P5443 [APIO2019]桥梁
如果没有修改,直接按照边权从大到小排序并查集维护即可。
带修就比较麻烦了,考虑分块:每个块内重构并查集,将询问和边从大到小排序,加边的时候如果这条边被修改那么跳过,每次询问的时候枚举所有被修改的边 \(e_i(u,v,d)\),如果修改时间在查询时间之前且是最后一次修改 \(e_i\) 且满足修改后的 \(d\geq w\) 或者修改时间在查询时间之前且修改前的 \(d\geq w\) 就往并查集里面加边 \((u,v)\),询问完后回溯。
设块大小为 \(S\),则时间复杂度为 \(\mathcal{O}(\frac{q}{S}S^2\log n+\frac{q}{S}m\log m)\),实际测试下 \(S\) 取 \(\sqrt{m\log m}\) 时跑得比较快。
#include<bits/stdc++.h>
using namespace std;
#define mem(x,v) memset(x,v,sizeof(x))
const int N=1e5+5;
const int S=1333;
int n,m,q,B,cnt,ans[S];
struct edge{
int u,v,d,id;
bool operator < (const edge &v) const {
return d>v.d;
}
}e[N],g[N];
struct query{
int t,x,y,id;
bool operator < (const query &v) const {
return y>v.y;
}
}c[N];
bool cmp(query a,query b){return a.id<b.id;}
int output;
int f[N],sz[N],tag[N];
int d,a[N],b[N],cha[N];
int find(int x){return f[x]==x?x:find(f[x]);}
int merge(int x,int y){
x=find(x),y=find(y);
if(x==y)return 0;
if(sz[x]<sz[y])swap(x,y);
sz[x]+=sz[y],f[y]=x,a[++d]=x,b[d]=y;
return 1;
}
void cancle(){sz[a[d]]-=sz[b[d]],f[b[d]]=b[d],d--;}
void solve(){
for(int i=1;i<=m;i++)g[i]=e[i];
sort(g+1,g+m+1),mem(tag,0),mem(ans,-1);
for(int i=1;i<=cnt;i++)if(c[i].t==1)tag[c[i].x]=1;
for(int i=1;i<=n;i++)f[i]=i,sz[i]=1;
sort(c+1,c+cnt+1),d=0;
for(int i=1,p=1;i<=cnt;i++)
if(c[i].t==2){
while(p<=m&&g[p].d>=c[i].y){
if(tag[g[p].id]){p++; continue;}
merge(g[p].u,g[p].v),p++;
} int can=0;
for(int j=1;j<=cnt;j++)
if(c[j].t==1&&c[j].id<c[i].id)
cha[c[j].x]=max(cha[c[j].x],c[j].id);
for(int j=1;j<=cnt;j++)
if(c[j].t==1){
int id=c[j].x;
if((!cha[id]&&e[id].d>=c[i].y)||
(cha[id]==c[j].id&&c[j].y>=c[i].y))
can+=merge(e[id].u,e[id].v);
} ans[c[i].id]=sz[find(c[i].x)];
for(int j=1;j<=cnt;j++)cha[c[j].x]=0;
while(can--)cancle();
}
sort(c+1,c+cnt+1,cmp);
for(int i=1;i<=cnt;i++){
if(c[i].t==1)e[c[i].x].d=c[i].y;
if(ans[i]!=-1)cout<<ans[i]<<"\n";
}
}
int main(){
cin>>n>>m,B=m?sqrt(m*log2(m)):2;
for(int i=1;i<=m;i++)cin>>e[i].u>>e[i].v>>e[i].d,e[i].id=i;
cin>>q;
for(int i=1;i<=q;i++){
cnt++,cin>>c[cnt].t>>c[cnt].x>>c[cnt].y,c[cnt].id=cnt;
if(cnt==B)solve(),cnt=0;
} if(cnt)solve();
return 0;
}
III. P3402 可持久化并查集
在公交车上写的代码(逃。
注意要维护两个数组的各个版本。
时间复杂度 \(n\log^2 n\)。
#include<bits/stdc++.h>
using namespace std;
const int N=1.5e5+5;
const int M=2e5+5;
int n,m,node,R[M],ls[N<<5],rs[N<<5],f[N<<5],sz[N<<5];
void build(int l,int r,int &x){
x=++node;
if(l==r)return f[x]=l,sz[x]=1,void();
int m=l+r>>1;
build(l,m,ls[x]),build(m+1,r,rs[x]);
} void modify(int pre,int &x,int l,int r,int p,int v,int tp){
x=++node,ls[x]=ls[pre],rs[x]=rs[pre],f[x]=f[pre],sz[x]=sz[pre];
if(l==r)return tp==1?f[x]=v:sz[x]=v,void();
int m=l+r>>1;
if(p<=m)modify(ls[pre],ls[x],l,m,p,v,tp);
else modify(rs[pre],rs[x],m+1,r,p,v,tp);
} int query(int l,int r,int p,int x,int tp){
if(l==r)return tp==1?f[x]:sz[x];
int m=l+r>>1;
if(p<=m)return query(l,m,p,ls[x],tp);
return query(m+1,r,p,rs[x],tp);
} int find(int x,int id){
int f=query(1,n,x,R[id],1);
return f==x?x:find(f,id);
} void merge(int x,int y,int id){
x=find(x,id),y=find(y,id);
if(x==y)return R[id+1]=R[id],void();
int sx=query(1,n,x,R[id],2),sy=query(1,n,y,R[id],2);
if(sx<sy)swap(x,y),swap(sx,sy);
modify(R[id],R[id+1],1,n,y,x,1);
modify(R[id+1],R[id+1],1,n,x,sx+sy,2);
}
int main(){
cin>>n>>m,build(1,n,R[0]);
for(int i=1,op,a,b;i<=m;i++){
cin>>op>>a;
if(op==2)R[i]=R[a];
if(op==1)cin>>b,merge(a,b,i-1);
if(op==3)R[i]=R[i-1],cin>>b,cout<<(find(a,i)==find(b,i))<<"\n";
}
return 0;
}
3. 可持久化 Trie
3.1. 算法简介
万物皆可可持久化。
类似可持久化线段树,插入一个数/字符串时新建节点。就不多讲了。
3.2. 应用
其实可持久化 Trie 的结构和**树几乎一模一样,只不过 Trie 中每个节点所维护的区间长度是 \(2\) 的幂。这样一来可持久化 Trie 能做的事情基本上完全*含于**树能做的事情。
3.3. 例题
*I. P5283 [十二省联考2019]异或粽子
首先将 \(a_i\) 的前缀异或和 \(b_i\) 按照 \(i\) 从小到大加入维护出现次数的 01Trie 并进行可持久化。即第 \(i\) 次操作后的 01Trie 为 \(T_i\)。
接下来对每个位置,求出以该位置为起点的最大异或和,即 \(b_{i\sim n}\) 与 \(b_{i-1}\) 异或的最大值 。查询方法:在 \(T_n-T_{i-1}\) 的 01Trie 上走与当前位相反的位,如果没有则走相同位。并记录当前考虑到以 \(i\) 为起点第几大的异或和,记为 \(s_i\)。显然 \(s_i\) 初始值为 \(1\)。
然后用堆维护每个位置及其对应的第 \(s_i\) 大异或和。每次取出最大的 \(i\) 使得以 \(i\) 为起点第 \(s_i\) 大的异或和最大,并将 \(s_i\) 加 \(1\)(如果不存在则忽略,即 \(s_i\) 已经与 \(n-i+1\) 相等),求出 \(i\) 所对应的值并更新堆。前 \(k\) 次取出的异或和之和即为答案。
时间复杂度 \(n\log n+k\log n\)。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define int unsigned int
#define pii pair <int,int>
#define fi first
#define se second
#define gc getchar()
inline int read() {
int x=0,sign=0; char s=gc;
while(!isdigit(s)) sign|=s=='-',s=gc;
while(isdigit(s)) x=(x<<1)+(x<<3)+(s-'0'),s=gc;
return sign?-x:x;
}
const int N=5e5+5;
const int K=N<<6;
struct node{
int val,id,kth;
bool friend operator < (node a,node b) {
return a.val==b.val?a.id<b.id:a.val<b.val;
}
};
priority_queue <node> s;
int cnt,R[N],sz[K],son[K][2];
void ins(int &x,int pre,int v,signed b=31){
x=++cnt,sz[x]=sz[pre]+1;
if(b<0)return;
int c=(v>>b)&1;
ins(son[x][c],son[pre][c],v,b-1),son[x][c^1]=son[pre][c^1];
}
int query(int x,int v,int k,int vv=0,signed b=31){
if(b<0)return v^vv;
int c=(v>>b)&1,s=sz[son[x][c^1]];
if(k<=s)query(son[x][c^1],v,k,vv^((c^1)<<b),b-1);
else query(son[x][c],v,k-s,vv^(c<<b),b-1);
}
int n,k,a[N],pre[N];
ll ans;
signed main(){
cin>>n>>k; ins(R[0],0,0);
for(int i=1;i<=n;i++)a[i]=read(),ins(R[i],R[i-1],pre[i]=pre[i-1]^a[i]);
for(int i=1;i<=n;i++)s.push({query(R[i-1],pre[i],1),i,1});
while(k--){
node it=s.top(); s.pop(),ans+=it.val;
if(++it.kth<=it.id)s.push({query(R[it.id-1],pre[it.id],it.kth),it.id,it.kth});
} cout<<ans<<endl;
return 0;
}
*II. CF241B Friends
题解。
III. P4735 最大异或和
可持久化 Trie 裸题。注意一开始要往 Trie 中加入一个 \(0\)。
时间复杂度 \(n\log n\)。
#include<bits/stdc++.h>
using namespace std;
#define gc getchar()
#define pc(x) putchar(x)
#define mem(x,v) memset(x,v,sizeof(x))
inline int read(){
int x=0; char s=gc;
while(!isdigit(s))s=gc;
while(isdigit(s))x=(x<<1)+(x<<3)+s-'0',s=gc;
return x;
} void print(int x){
if(x<10)return pc(x+'0'),void();
print(x/10),pc(x%10+'0');
}
const int N=6e5+5;
const int B=24;
int n,m,a,node;
int R[N],ls[N<<5],rs[N<<5],val[N<<5];
void ins(int pre,int &x,int a,int b){
val[x=++node]=val[pre]+1,ls[x]=ls[pre],rs[x]=rs[pre];
if(b==0)return;
if((a>>b-1)&1)ins(rs[pre],rs[x],a,b-1);
else ins(ls[pre],ls[x],a,b-1);
} int query(int x,int y,int v,int b){
if(b==0)return 0;
int bit=(v>>b-1)&1;
if(bit){
if(val[ls[y]]-val[ls[x]])return (1<<b-1)+query(ls[x],ls[y],v,b-1);
else return query(rs[x],rs[y],v,b-1);
} else{
if(val[rs[y]]-val[rs[x]])return (1<<b-1)+query(rs[x],rs[y],v,b-1);
else return query(ls[x],ls[y],v,b-1);
}
}
int main(){
n=read()+1,m=read(),ins(R[0],R[1],0,B);
for(int i=2;i<=n;i++)ins(R[i-1],R[i],a^=read(),B);
for(int i=1,l,r;i<=m;i++){
char s; cin>>s;
if(s=='A')n++,ins(R[n-1],R[n],a^=read(),B);
else l=read(),r=read(),print(query(R[l-1],R[r],a^read(),B)),pc('\n');
}
return 0;
}