update:2021.6.30
文章目录
并查集
程序自动分析
解释
把相同的直接加入到一个集合里面,然后再之后判断他们是否真正相同. 注意离散化。
模板
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6 + 10;
typedef pair<int,int> pii;
int fa[N];
int find(int x){
if(x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
vector<int> alls;
vector<pii> a,b;
int findp(int x){
return lower_bound(alls.begin(),alls.end(),x) - alls.begin();
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int tt; cin>>tt;
while(tt --){
int n; cin>>n;
alls.clear();a.clear();b.clear();
bool plas = true;
for(int i=1;i<=n;i++){
int x,y,e; cin>>x>>y>>e;
if(e) a.push_back(make_pair(x,y));
else b.push_back(make_pair(x,y));
alls.push_back(x);
alls.push_back(y);
}
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
for(int i=0;i<=(int)alls.size();i++) fa[i] = i;
for(auto [i,j] : a){
i = find(findp(i)),j = find(findp(j));
fa[i] = j;
}
for(auto [i,j] : b){
i = find(findp(i)),j = find(findp(j));
if(i == j){
plas = false;
break;
}
}
if(!plas) cout<<"NO"<<endl;
else cout<<"YES"<<endl;
}
return 0;
}
更多例题
银河英雄传说 维护距离的并查集。
树状数组
楼兰图腾
解释
维护当前这个点左边比它小(大)的,右边比它小(大)的,乘积之和就是答案。
模板
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;
#define lowbit(x) ((x) & (-x))
int c[N],a[N],A[N],V[N],n;
void update(int x,int y){
for(int i=x;i<=n;i+=lowbit(i)) c[i] += y;
}
int ask(int x){
int res = 0;
for(int i=x;i;i-=lowbit(i)) res += c[i];
return res;
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=n;i>=1;i--){
V[i] = ask(n) - ask(a[i]-1);
A[i] = ask(a[i]-1);
update(a[i],1);
}
memset(c,0,sizeof(c));
int ansV = 0,ansA = 0;
for(int i=1;i<=n;i++){
ansV += V[i] * (ask(n) - ask(a[i]-1));
ansA += A[i] * ask(a[i]-1);
update(a[i],1);
}
cout<<ansV<<" "<<ansA<<endl;
return 0;
}
更多例题
P1972洛谷 离线处理,按右端点排序,有一个性质,每个数的权值只与最右边出现的有关。代码
一个简单的整数问题 区间修改 + 单点查询,可用线段树懒标记。代码
一个简单的整数问题2 区间修改 + 区间查询。在前一题的基础上化简一个公式。代码
线段树
常用知识点。 区间最值,区间求和,区间染色,矩形问题,区间k大数,二维线段树,三维线段树。
区间修改(加 & 乘)
解释
多了一个乘法操作,可以考虑优先级。每次先算乘法。
首先,对于一个区间(和为s) 假设已经按 +a , 乘b进行了操作。值得到的值为( (s + a) * b )-> sb + ab 假设先乘得到(sb + a )这样相比,add应该还要再乘上一个b才对,所以,当更新到一个区间时,
为了进行先乘的操作而不让结果发生变化,应该将add乘上当前乘的值。 这个就是update里面更新乘法时候应该进行的操作。
push_down也是这样更新子区间的add,mul直接乘上。
初始状态mul为1,add为0。
模板
这题mod是输入的
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
typedef long long ll;
#define l(x) t[x].l
#define r(x) t[x].r
#define add(x) t[x].add
#define mul(x) t[x].mul
#define sum(x) t[x].sum
int n,m,mod;
int a[N];
struct SegmentTree{
int l,r;
ll add,mul=1,sum;
}t[N*4];
void push_up(int p){
sum(p) = (sum(p*2) + sum(p*2+1)) % mod;
}
void push_down(int p){
sum(p*2) = (sum(p*2) * mul(p) % mod + (r(p*2) - l(p*2) + 1) * add(p)) % mod;
sum(p*2+1) = (sum(p*2+1) * mul(p) % mod + (r(p*2+1) - l(p*2+1) + 1) * add(p)) % mod;
mul(p*2) = (mul(p) * mul(p*2)) % mod;
mul(p*2+1) = (mul(p) * mul(p*2+1)) % mod;
add(p*2) = (add(p*2) * mul(p) + add(p)) % mod;
add(p*2+1) = (add(p*2+1) * mul(p) + add(p)) % mod;
add(p) = 0;
mul(p) = 1;
}
void build(int p,int l,int r){
l(p) = l,r(p) = r;
if(l == r){
sum(p) = a[l];
return ;
}
int mid = (l + r) >> 1;
build(p*2,l,mid);build(p*2+1,mid+1,r);
push_up(p);
}
void update(int p,int l,int r,int v,int id){
if(l <= l(p) && r >= r(p)){
if(!id){
sum(p) = (sum(p) * v) % mod;
add(p) = (add(p) * v) % mod;
mul(p) = (mul(p) * v) % mod;
}else{
sum(p) = (sum(p) + (r(p) - l(p) + 1) * v) % mod;
add(p) = (add(p) + v) % mod;
}
return ;
}
push_down(p);
int mid = (l(p) + r(p)) >> 1;
if(l <= mid) update(p*2,l,r,v,id);
if(r > mid) update(p*2+1,l,r,v,id);
push_up(p);
}
ll ask(int p,int l,int r){
if(l <= l(p) && r >= r(p)) return sum(p);
push_down(p);
ll val = 0;
int mid = (l(p) + r(p)) >> 1;
if(l <= mid) val = (val + ask(p*2,l,r)) % mod;
if(r > mid) val = (val + ask(p*2+1,l,r)) % mod;
return val;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>m>>mod;
for(int i=1;i<=n;i++) cin>>a[i];
build(1,1,n);
while(m --){
int t; cin>>t;
if(t == 1){
int x,y,d; cin>>x>>y>>d;
update(1,x,y,d,0);
}else if(t == 2){
int x,y,d; cin>>x>>y>>d;
update(1,x,y,d,1);
}else{
int x,y; cin>>x>>y;
cout<<ask(1,x,y)<<endl;
}
}
return 0;
}
区间染色
解释
另外的叫法 区间修改为同一个值
**注意区间离散化问题 ** ,在每个数后面都加一个数去填补空格区域。
懒标记法。
加一个color变量,表示当前区间是由哪个颜色染成,当为-1时,表示这个区间是混合颜色。可以继续往下分。
push_up : 子区间不一样,可以让父亲区间为-1,否则和子区间相同
push_down :当父区间为-1时,儿子区间不修改。否则修改为和父区间一样
ask :当一个区间颜色不为-1,则标记当前区间染的色,否则可以继续分解下去
其它部分就是标准模板
倒序插入
设置变量color表示当前区间是否全部被染色
倒序遍历,修改区间,当前区间修改的时候看是否能成功染色,成功染色的标志是存在一个子区间没有被完全染色。成功则计数
push_up :父亲区间是否被全部染色取决于两个儿子区间是否被全部染色
模板(懒标记法)
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 1e4 + 10;
typedef long long ll;
#define l(x) t[x].l
#define r(x) t[x].r
#define color(x) t[x].color
int n,m;
int a[N],b[N];
vector<int> c;
bool vis[N];
struct SegmentTree{
int l,r,color;
}t[N*4];
int find(int x){
return lower_bound(c.begin(),c.end(),x)-c.begin();
}
void push_up(int p){
if(color(p*2) == color(p*2+1) && color(p*2) != -1) color(p) = color(p*2);
else color(p) = -1;
}
void push_down(int p){
if(color(p) != -1) color(p*2) = color(p*2+1) = color(p);
}
void build(int p,int l,int r){
l(p) = l,r(p) = r;
if(l == r) return ;
int mid = (l + r) >> 1;
build(p*2,l,mid);build(p*2+1,mid+1,r);
}
void update(int p,int l,int r,int id){
if(l <= l(p) && r >= r(p)){
color(p) = id;
return ;
}
push_down(p);
int mid = (l(p) + r(p)) >> 1;
if(l <= mid) update(p*2,l,r,id);
if(r > mid) update(p*2+1,l,r,id);
push_up(p);
}
int ask(int p,int l,int r){
if(color(p) != -1){
if(!vis[color(p)]){
vis[color(p)] = true;
return 1;
}
return 0;
}
push_down(p);
int mid = (l(p) + r(p)) >> 1,val = 0;
if(l <= mid) val += ask(p*2,l,r);
if(r > mid) val += ask(p*2+1,l,r);
return val;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a[i]>>b[i];
c.push_back(a[i]);
c.push_back(a[i]+1);
c.push_back(b[i]);
c.push_back(b[i]+1);
}
sort(c.begin(),c.end());
c.erase(unique(c.begin(),c.end()),c.end());
for(int i=1;i<=m;i++) a[i] = find(a[i])+1,b[i] = find(b[i])+1;
build(1,1,(int)c.size());
for(int i=1;i<=m;i++) update(1,a[i],b[i],i);
vis[0] = true;
cout<<ask(1,1,(int)c.size())<<endl;
return 0;
}
模板(倒序插入维护区间染色)
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 1e4 + 10;
typedef long long ll;
#define l(x) t[x].l
#define r(x) t[x].r
#define color(x) t[x].color
int n,m;
int a[N],b[N];
vector<int> c;
struct SegmentTree{
int l,r,color;
}t[N*4];
int find(int x){
return lower_bound(c.begin(),c.end(),x)-c.begin();
}
void push_up(int p){
color(p) = color(p*2) & color(p*2+1);
}
void build(int p,int l,int r){
l(p) = l,r(p) = r;
if(l == r) return ;
int mid = (l + r) >> 1;
build(p*2,l,mid);build(p*2+1,mid+1,r);
}
int update(int p,int l,int r){
if(color(p)) return 0;
if(l <= l(p) && r >= r(p)) return color(p) = 1;
int mid = (l(p) + r(p)) >> 1,val = 0;
if(l <= mid) val |= update(p*2,l,r);
if(r > mid) val |= update(p*2+1,l,r);
push_up(p);
return val;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a[i]>>b[i];
c.push_back(a[i]);
c.push_back(a[i]+1);
c.push_back(b[i]);
c.push_back(b[i]+1);
}
sort(c.begin(),c.end());
c.erase(unique(c.begin(),c.end()),c.end());
for(int i=1;i<=m;i++) a[i] = find(a[i])+1,b[i] = find(b[i])+1;
build(1,1,(int)c.size()+10);
int ans = 0;
for(int i=m;i>=1;i--) if(update(1,a[i],b[i])) ans ++;
cout<<ans<<endl;
return 0;
}
更多例题
数学计算 (就是标准单点修改,区间查询,维护区间乘积)
小白逛公园 (区间维护连续子序列的最大和)
无聊的序列 (等差数列,维护差分)
HDU2795 直接维护区间最大值就行了
区间最大公约数 要用到更相减损术,维护差分序列,并且用树状数组维护原序列前缀和。
扫描线
参考博客:
博客 按x或者按y扫描都一样
亚特兰蒂斯
解释
求面积模板,代码注释。此模板是按x扫描
模板
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
double raw[N];// raw对应离散化之前的数
int cnt = 0;
map<double,int> val;// 对应离散化之后的数。
struct Point{
double x,y1,y2;
int k;
bool operator <(const Point&T)const{
return x < T.x;
}
}p[N];
#define l(x) t[x].l
#define r(x) t[x].r
#define cnt(x) t[x].cnt
#define len(x) t[x].len
struct SegmentTree{
int l,r,cnt;
double len;
}t[N << 2];
void push_up(int p,int l,int r){
if(cnt(p)) len(p) = raw[r+1] - raw[l];
else if(l == r) len(p) = 0;
else len(p) = len(p<<1) + len(p<<1|1);
}
void build(int p,int l,int r){
l(p) = l,r(p) = r;cnt(p) = len(p) = 0;
if(l == r) return ;
int mid = (l + r) >> 1;
build(p<<1,l,mid);build(p<<1|1,mid+1,r);
}
void update(int p,int l,int r,int k){
if(l <= l(p) && r(p) <= r){
cnt(p) += k;
push_up(p,l(p),r(p));
return ;
}
int mid = (l(p) + r(p)) >> 1;
if(l <= mid) update(p<<1,l,r,k);
if(r > mid) update(p<<1|1,l,r,k);
push_up(p,l(p),r(p));
}
signed main(){
IOS
int n,kase = 1;
while(cin>>n && n){
cnt = 0;
val.clear();
for(int i=0;i<n;i++){
double x1,x2,y1,y2; cin>>x1>>y1>>x2>>y2;
p[++ cnt] = {x1,y1,y2,1};
raw[cnt] = y1;
p[++ cnt] = {x2,y1,y2,-1};
raw[cnt] = y2;
}
sort(p+1,p+cnt+1);
// 离散化
sort(raw+1,raw+cnt+1);
int len = unique(raw+1,raw+cnt+1) - raw - 1;
for(int i=1;i<=len;i++) val[raw[i]] = i;
build(1,1,len-1);
double ans = 0;
for(int i=1;i<cnt;i++){// 注意l~r区间维护的是raw[r+1] - raw[l] 的值
int l = val[p[i].y1],r = val[p[i].y2]-1,k = p[i].k;
update(1,l,r,k);
ans += len(1) * (p[i+1].x - p[i].x);
}
cout<<"Test case #"<<kase++<<"\nTotal explored area: "<<fixed<<setprecision(2)<<ans<<"\n"<<endl;
}
return 0;
}
可持久化线段树(主席树)
参考博客:
【模板】可持久化线段树1(可持久化数组)
解释
无(看板子就行了)
模板
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n,m;
int a[N],rt[N];
#define l(x) t[x].l
#define r(x) t[x].r
#define val(x) t[x].val
struct LSegmentTree{
int l,r,val;
}t[N << 5];
int idx = 0;
int build(int l,int r){
int root = ++idx;
if(l == r){
val(root) = a[l];
return root;
}
int mid = (l + r) >> 1;
l(root) = build(l,mid);
r(root) = build(mid+1,r);
return root;
}
int update(int p,int l,int r,int pos,int va){
int root = ++idx;
l(root) = l(p),r(root) = r(p);
int mid = (l + r) >> 1;
if(l == r){
val(root) = va;
return root;
}
if(pos <= mid) l(root) = update(l(root),l,mid,pos,va);
else r(root) = update(r(root),mid+1,r,pos,va);
return root;
}
int query(int p,int l,int r,int pos){
int mid = (l + r) >> 1;
if(l == r) return val(p);
if(pos <= mid) return query(l(p),l,mid,pos);
else return query(r(p),mid+1,r,pos);
}
signed main(){
IOS
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
rt[0] = build(1,n);
for(int i=1;i<=m;i++){
int nrt,op; cin>>nrt>>op;
if(op == 1){
int pos,val; cin>>pos>>val;
rt[i] = update(rt[nrt],1,n,pos,val);
}else{
int pos; cin>>pos;
cout<<query(rt[nrt],1,n,pos)<<endl;
rt[i] = rt[nrt];
}
}
return 0;
}
【模板】可持久化线段树2(主席树)
解释
权值线段树加历史维护。 f ( i ) f(i) f(i) 表示第 i i i个版本,权值线段树维护的值。
利用前缀思想,求 [ l , r ] [l,r] [l,r]的第k小值等价于:权值线段树维护的信息 [ 1 , r ] − [ 1 , l − 1 ] [1,r] - [1,l-1] [1,r]−[1,l−1] ,即 f ( r ) − f ( l − 1 ) f(r) - f(l-1) f(r)−f(l−1) 。
参考博客:OIWiki
模板
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n,m,len;
int a[N],rt[N],b[N];
#define l(x) t[x].l
#define r(x) t[x].r
#define sum(x) t[x].sum
struct LSegmentTree{
int l,r,sum;
}t[N << 5];
int idx = 0;
int find(int x){
return lower_bound(b+1,b+len+1,x)-b;
}
int build(int l,int r){
int root = ++idx;
if(l == r) return root;
int mid = (l + r) >> 1;
l(root) = build(l,mid);
r(root) = build(mid+1,r);
return root;
}
int update(int p,int l,int r,int k){
int root = ++idx;
l(root) = l(p),r(root) = r(p),sum(root) = sum(p) + 1;
int mid = (l + r) >> 1;
if(l == r) return root;
if(k <= mid) l(root) = update(l(root),l,mid,k);
else r(root) = update(r(root),mid+1,r,k);
return root;
}
int query(int lp,int rp,int l,int r,int k){
int mid = (l + r) >> 1,suml = sum(l(rp)) - sum(l(lp));
if(l == r) return l;
if(k <= suml) return query(l(lp),l(rp),l,mid,k);
else return query(r(lp),r(rp),mid+1,r,k-suml);
}
signed main(){
IOS
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i],b[i] = a[i];
sort(b+1,b+n+1);
len = unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;i++) a[i] = find(a[i]);
rt[0] = build(1,len);
for(int i=1;i<=n;i++) rt[i] = update(rt[i-1],1,len,a[i]);
while(m --){
int l,r,k; cin>>l>>r>>k;
cout<<b[query(rt[l-1],rt[r],1,len,k)]<<endl;
}
return 0;
}
单点修改区间查询第k小值
解释
考虑静态的时候,如上,只需要求的版本前缀和,就可以快速求得第k小值。当涉及到单点修改时,可以把每一棵树看成一个点,用树状数组进行维护,就可以快速修改前缀和。查询稍做修改,对比静态,每次查询都是用的当前区间的所有树根,具体看代码 参考博客
模板
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
#define ls(x) t[x].ls
#define rs(x) t[x].rs
#define cnt(x) t[x].cnt
struct LSegmentTree{
int ls,rs,cnt;
}t[N * 500];
int idx = 0;
int rt[N],a[N],len;
int n,m;
vector<int> ve;
struct Query{
char op;
int l,r,k;
}q[N];
int find(int x){
return lower_bound(ve.begin(),ve.end(),x)-ve.begin() + 1;
}
// 主席树
int update(int p,int l,int r,int pos,int val){
int root = ++idx;
ls(root) = ls(p),rs(root) = rs(p),cnt(root) = cnt(p) + val;
if(l == r) return root;
int mid = (l + r) >> 1;
if(pos <= mid) ls(root) = update(ls(root),l,mid,pos,val);
else rs(root) = update(rs(root),mid+1,r,pos,val);
return root;
}
int rt1[N],rt2[N],cnt1,cnt2;
int query(int l,int r,int k){
if(l == r) return ve[l-1]; // 下标从0开始
int mid = (l + r) >> 1;
int sum = 0;
for(int i=0;i<cnt1;i++) sum -= cnt(ls(rt1[i])); // 类比静态
for(int i=0;i<cnt2;i++) sum += cnt(ls(rt2[i]));
if(k <= sum){
for(int i=0;i<cnt1;i++) rt1[i] = ls(rt1[i]);
for(int i=0;i<cnt2;i++) rt2[i] = ls(rt2[i]);
return query(l,mid,k);
}else{
for(int i=0;i<cnt1;i++) rt1[i] = rs(rt1[i]);
for(int i=0;i<cnt2;i++) rt2[i] = rs(rt2[i]);
return query(mid+1,r,k-sum);
}
}
// 树状数组
#define lowbit(x) ((x) & (-x))
void change(int x,int p,int val){
for(int i=x;i<=n;i+=lowbit(i)) rt[i] = update(rt[i],1,len,p,val);
}
int ask(int l,int r,int k){
cnt1 = cnt2 = 0;
for(int i=l;i;i-=lowbit(i)) rt1[cnt1++] = rt[i];
for(int i=r;i;i-=lowbit(i)) rt2[cnt2++] = rt[i];
return query(1,len,k);
}
signed main(){
IOS
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i],ve.push_back(a[i]);
for(int i=1;i<=m;i++){
cin>>q[i].op;
if(q[i].op == 'C'){
int x,y; cin>>x>>y;
q[i].l = x,q[i].r = y;
ve.push_back(y);
}else{
int l,r,k; cin>>l>>r>>k;
q[i].l = l,q[i].r = r,q[i].k = k;
}
}
// 离散化
sort(ve.begin(),ve.end());
ve.erase(unique(ve.begin(),ve.end()),ve.end());
len = ve.size();
for(int i=1;i<=n;i++) a[i] = find(a[i]);
for(int i=1;i<=m;i++) if(q[i].op == 'C') q[i].r = find(q[i].r);
for(int i=1;i<=n;i++) change(i,a[i],1);
for(int i=1;i<=m;i++){
char op = q[i].op;
if(op == 'Q'){
int l = q[i].l,r = q[i].r,k = q[i].k;
cout<<ask(l-1,r,k)<<endl;
}else{
int x = q[i].l,y = q[i].r;
change(x,a[x],-1);
a[x] = y;// 记住在原数组修改
change(x,a[x],1);
}
}
return 0;
}
更多例题
P1383 可持久化数组维护,记录每一个版本插入的长度,undo操作相当于把距离当前x个版本的根节点给它 代码
可持久化字典树
参考博客:
OIWiki 例题所给的代码被数据加强给卡掉了,但是不影响理解。
最大异或和
解释
设s(x) 表示1 ~ x 的异或和。根据异或性质,题目所求max( a( p ) ^ … ^ a( n ) ^ x) -> s( p - 1 ) ^ s ( n ) ^ x
而 s ( n ) ^ x 是一个定值,所以只需要知道s ( p - 1) 即 s ( x ) ( x > = l − 1 ) a n d ( x < = r − 1 ) (x >= l-1) and (x <= r-1) (x>=l−1)and(x<=r−1) .注意,
s ( 0 ) 为 0 . 用主席树的思想,维护一个前缀,每一个s ( x ) 每一位0,1出现的次数,在查询的时候,如果接下来访问的那一位有想要的0或者1,则累加答案。即实现可持久化权值线段树,每个节点代表位,节点存两个信息,1出现的次数和0出现的次数。
但是这题是用字典树来完成。想法差不多,只是维护字典树用val数组维护(代表当前位数当前值(0,1)出现的次数的前缀和),不用开权值线段树。
模板
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
const int N = 6e5 + 10;
int n,m;
int cnt = 0;
int rt[N],s[N],son[N*29][2],val[N*29];
void insert(int lp,int rp,int v){ // 插入
for(int i=28;i>=0;i--){
val[rp] = val[lp] + 1;// 维护两个版本之间相同“含义(代表值)”所出现的次数,用于维护前缀和。
int t = (v & (1 << i)) ? 1 : 0;
son[rp][!t] = son[lp][!t];
son[rp][t] = ++cnt;
lp = son[lp][t];
rp = son[rp][t];
}
val[rp] = val[lp] + 1;
}
int query(int lp,int rp,int v){
int res = 0;
for(int i=28;i>=0;i--){
int t = (v & (1 << i)) ? 1 : 0;
if(val[son[rp][!t]] - val[son[lp][!t]] > 0){// 当前区间这个位置代表值有元素
res += (1 << i);
rp = son[rp][!t];
lp = son[lp][!t];
}else{
rp = son[rp][t];
lp = son[lp][t];
}
}
return res;
}
signed main(){
IOS
cin>>n>>m;
rt[0] = ++cnt,insert(0,rt[0],0); // 预先把0处理进来,当l为1时,s0=0。
for(int i=1;i<=n;i++){
cin>>s[i],s[i] ^= s[i-1];
rt[i] = ++cnt,insert(rt[i-1],rt[i],s[i]);
}
while(m --){
string op; cin>>op;
if(op == "A"){
cin>>s[++n];
s[n] ^= s[n-1];
rt[n] = ++cnt;
insert(rt[n-1],rt[n],s[n]);
}else{
int l,r,k; cin>>l>>r>>k;
l -- ,r -- ;
if(l == 0) cout<<query(0,rt[r],s[n]^k)<<endl;
else cout<<query(rt[l-1],rt[r],s[n]^k)<<endl;
}
}
return 0;
}