https://www.luogu.com.cn/problem/P4735
https://darkbzoj.tk/problem/3261
设 (sum_i) 表示前 (i) 个数的异或和,转换那个式子为 (sum_n operatorname{XOR} x operatorname{XOR} sum_i,i in [l-1,r-1])
其中的 (sum_n operatorname{XOR} x operatorname{XOR}) 是个定值,也就是找一个 (i) 使得这个式子最大
由于这是异或运算,从高位向低位考虑,贪心的让每一位尽量和 (sum_n operatorname{XOR} x operatorname{XOR}) 的结果不一样
可以把这 (n) 个 (sum) 值插入到一个 trie 上
就是如果第 (pos) 位结果是 (k),那么如果 ([l-1,r-1]) 中有一个 (sum_j) 使得它的第 (pos) 位是 (k operatorname{XOR} 1),那么就选他,答案加 (2^{pos}),否则选另一个,答案不加
然后想看区间内某个位置上 (1/0) 的数有没有,其实就是看这个位上是 (1/0) 的数的个数的前缀和,这就用到了 trie 的可持久化,每插入一个数时,对于某一位,把它所对应的那个数字的儿子(比如这个位置是 (1),就是 son[1]
)开一个新的内存,个数加一,另一个儿子还是指向原来那个树的儿子(同时个数也就不变)
其实就是 (n) 个根,每个根都对应一个完整的树,但这些树会有一些节点是共用的
要注意在最前面插入一个 (0),来应对 (l=0) 的情况,由于每个新插入的数的每一位都要开辟一个新内存,所以空间是 (O((n+m)log a_i)) 的
为了防止判断空指针带来的代码细节增多和常数增大,用一个自己设定的 null
来作为空指针
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN puts("")
inline long long read(){
register long long x=0;register int y=1;
register char c=std::getchar();
while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
return y?x:-x;
}
int n,m;
struct tr{
tr *son[2];
int cnt;
}dizhi[20000006],*root[600006],*null;
int tot=-1;
inline tr *New(){
tr *ret=&dizhi[++tot];
ret->son[0]=ret->son[1]=null;
return ret;
}
void build(tr *last,tr *tree,int num,int pos){
tree->cnt=last->cnt+1;
if(pos<0) return;
int nex=(num>>pos)&1;
tree->son[nex]=New();
tree->son[nex^1]=last->son[nex^1];
build(last->son[nex],tree->son[nex],num,pos-1);
}
int ask(tr *left,tr *right,int num,int pos){
if(pos<0) return 0;
int nex=((num>>pos)&1)^1;
// printf("right->son[0]=%d , right->son[1]=%d
",right->son[0]->cnt,right->son[1]->cnt);
// printf("left->son[0]=%d , left->son[1]=%d
",left->son[0]->cnt,left->son[1]->cnt);
if(right->son[nex]->cnt>left->son[nex]->cnt) return (1<<pos)+ask(left->son[nex],right->son[nex],num,pos-1);
return ask(left->son[nex^1],right->son[nex^1],num,pos-1);
}
#define MAX 24
int main(){
n=read();m=read();
int sum=0;
null=&dizhi[++tot];
null->son[0]=null->son[1]=null;
root[0]=New();
root[1]=New();//新加入一个 root[1],值是 0,以后插入的 a[i] 存入 root[i+1]
build(root[0],root[1],0,MAX);
n++;
for(reg int i=2;i<=n;i++){
sum^=read();
root[i]=New();build(root[i-1],root[i],sum,MAX);
}
reg char c;reg int l,r;
while(m--){
c=getchar();
while(c!='Q'&&c!='A') c=getchar();
if(c=='A'){
root[++n]=New();
sum^=read();
build(root[n-1],root[n],sum,MAX);
}
else{
l=read();r=read();
printf("%d
",ask(root[l-1],root[r],sum^read(),MAX));
//其实是查询区间 [l-1,r-1],因为是前缀和所以是 [l-2,r-1],但由于最前面插入了一个 0,整体后移一个,是 [l-1,r]
}
}
return 0;
}