题目
题目链接:https://www.luogu.com.cn/problem/P4735
给定一个非负整数序列 ({a}),初始长度为(n)。
有 (m) 个操作,有以下两种操作类型:
A x
:添加操作,表示在序列末尾添加一个数 (x),序列的长度 (n+1)。Q l r x
:询问操作,你需要找到一个位置 (p),满足(l le p le r),使得: (a[p] oplus a[p+1] oplus ... oplus a[N] oplus x) 最大,输出最大是多少。
(n,mleq 300000,0leq a_ileq 10^7)。
思路
可持久化 Trie 板子题。到现在才会这个玩意退役了退役了。
和可持久化线段树基本一样的思路,每次插入的时候新建节点,然后将上一个版本的点复制过来,然后继续往下递归处理。
回到本题,显然做前缀和,问题转化为在 ([l-1,r-1]) 中找到一个位置 (p) 使得 (a_p mathrm{xor} a_n mathrm{xor} x) 最大。
那么就直接在版本 (r) 的 Trie 上查询就可以解决右端点,为了确保不要查询到左端点,可以在 Trie 树上每一个节点维护一个 (last) 表示这棵子树中,所表示的数的下表最大的值,然后每次查询的时候只往 (lastgeq l-1) 的走就可以了。
时间复杂度 (O(mlog n))。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1000010,LG=25;
int n,m,a[N],rt[N];
char opt[3];
struct Trie
{
int tot,ch[N*LG][2],last[N*LG];
Trie() { tot=1; rt[0]=1; }
int ins(int dep,int now,int v,int k)
{
int x=++tot,id=((v>>dep)&1);
ch[x][0]=ch[now][0]; ch[x][1]=ch[now][1]; last[x]=k;
if (dep<0) return x;
ch[x][id]=ins(dep-1,ch[now][id],v,k);
return x;
}
int query(int dep,int x,int l,int v)
{
if (dep<0) return a[last[x]];
int id=((v>>dep)&1);
if (ch[x][id^1] && last[ch[x][id^1]]>=l)
return query(dep-1,ch[x][id^1],l,v);
else
return query(dep-1,ch[x][id],l,v);
}
}trie;
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]); a[i]^=a[i-1];
rt[i]=trie.ins(LG,rt[i-1],a[i],i);
}
while (m--)
{
scanf("%s",opt);
if (opt[0]=='A')
{
scanf("%d",&a[++n]); a[n]^=a[n-1];
rt[n]=trie.ins(LG,rt[n-1],a[n],n);
}
else
{
int l,r,x;
scanf("%d%d%d",&l,&r,&x);
int y=trie.query(LG,rt[r-1],l-1,a[n]^x);
printf("%d
",a[n]^x^y);
}
}
return 0;
}