P4735 最大异或和
题目
可持久化Trie模板题
维护一棵0/1Trie:
每个节点t[x][y]维护两个值:cnt 和 id。
cnt :表示到x这个节点所在的根(可理解为时间),y(0/1)出现了几次(前缀和);
id :表示当前数字(x,y)被存在哪个容器中(相当于一个指针)
可持久化Trie:
插入方式与普通Trie基本相同:
对于一个节点,新建一个点表示当前节点:
对于存在的值:t[now][x]={t[last][x].cnt+1,++tot};
对于不存在的值 t[now][x1]=t[last][x1];(直接复制过来)
查询与可持久化线段树类似
---------------------------------------------------------------------------------
对于本题:我们每次对 pos 这个位置插入的是 [1,x] 区间的所有数的异或
对于每一次查询:很容易想到我们要按二进制从高往低贪心。
对于 x 的第 len 位 x_len,如果在 [p,r] 的所有数的异或中(设为 y )能找到 x_len^1。
则说明 y_len^x_len=1,即 ans的第len位可取1,根据贪心,我们应该取
而这题有一个很神奇的地方:
if(t[l][y^1].cnt<t[r][y^1].cnt) { // 当前为第len位 return (1<<len)+query(t[l][y^1].id,t[r][y^1].id,len-1,x); }
只要满足 if 中的条件,则说明在[l,r]的a中(原题目意义下),出现过能改变 len 这一位的数。
而这个神奇的性质就是:
既然它可以使得前缀改变,那它也一定能改变后缀
所以只要满足上述条件,则说明y中有它.
剩下就是一些细节了
code:
#include<bits/stdc++.h> const int N=3e5+5; using namespace std; char c[10]; int n,m,tot; int rt[N],a[N]; struct Trie{ int cnt,id; }t[N*30][2]; void ins(int now,int last,int len,int x) { if(len<0)return ; int y=(x>>len)&1;// (当前数字对1的xor) t[now][y^1]=t[last][y^1];//对于这一位没有的数字,将前面的节点复制过来 t[now][y].id=++tot; t[now][y].cnt=t[last][y].cnt+1;//对于这一位上的"当前数字",记录前缀和的个数 ins(t[now][y].id,t[last][y].id,len-1,x); } int query(int l,int r,int len,int x) { if(len<0)return 0; int y=(x>>len)&1; if(t[l][y^1].cnt<t[r][y^1].cnt)//y^1在[l,r]之间出现过,根据贪心,取了y^1与y异或一定最优 { // 当前为第len位 return (1<<len)+query(t[l][y^1].id,t[r][y^1].id,len-1,x); } else { return query(t[l][y].id,t[r][y].id,len-1,x); } } void work() { cin>>n>>m; rt[0]=++tot; ins(rt[0],0,25,0); for(int i=1,x;i<=n;i++) { scanf("%d",&x); a[i]=a[i-1]^x; rt[i]=++tot; ins(rt[i],rt[i-1],25,a[i]); } for(int i=1,x,l,r,ans;i<=m;i++) { scanf("%s",c+1); if(c[1]=='A') { scanf("%d",&x); rt[++n]=++tot; a[n]=a[n-1]^x; ins(rt[n],rt[n-1],25,a[n]); } else { scanf("%d%d%d",&l,&r,&x); l--,r--;//序列从0开始 x^=a[n]; if(l==0) { ans=query(0,rt[r],25,x); } else { //l-1:当前可行的区间是[l,r],所以把l-1当作前缀 ans=query(rt[l-1],rt[r],25,x); } printf("%d\n",ans); } } } int main() { work(); }