可持久化数据结构

标 * 是推荐做的题目。

1. 可持久化线段树(**树)

zhu xi 竟然被和谐了。

1.1. 算法简介

OI-Wiki

注意到单点修改线段树时,最多只会遍历到 \(\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;
}
posted @ 2021-08-01 15:23  qAlex_Weiq  阅读(1696)  评论(1编辑  收藏  举报