suxxsfe

一言(ヒトコト)

P4735 最大异或和(可持久化 trie)

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\n",right->son[0]->cnt,right->son[1]->cnt);
//		printf("left->son[0]=%d , left->son[1]=%d\n\n",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\n",ask(root[l-1],root[r],sum^read(),MAX));
			//其实是查询区间 [l-1,r-1],因为是前缀和所以是 [l-2,r-1],但由于最前面插入了一个 0,整体后移一个,是 [l-1,r]
		}
	}
	return 0;
}
posted @ 2020-10-06 23:44  suxxsfe  阅读(112)  评论(0编辑  收藏  举报