bzoj3261 最大异或和
原题地址:http://www.lydsy.com/JudgeOnline/problem.php?id=3261
题意:
给定一个非负整数序列 {a},初始长度为 N。
有 M个操作,有以下两种操作类型:
1 、A x:添加操作,表示在序列末尾添加一个数 x,序列的长度 N+1。
2 、Q l r x:询问操作,你需要找到一个位置 p,满足 l<=p<=r,使得:
a[p] xor a[p+1] xor … xor a[N] xor x 最大,输出最大是多少。
数据范围
N,M<=300000 ,0<=a[i]<=10^7
题解:
(重写数组版…)
因为有Add操作,维护后缀的Trie是困难的,于是考虑转化成前缀。
那么:
令pre[i]=a[1]^a[2]^……a[i]
a[p] ^ a[p+1] ^ … ^ a[N] ^ x=pre[p-1]^pre[n]^x
于是变成可持久化Trie维护各个位置的前缀异或和,在其中查询x^pre[n]与这些前缀中的一个能够异或出的最大值。
注意:
原来查询 [L,R]区间,转化后是 [L-1,R-1]的前缀,然后可持久化Trie又要用到root[(L-1)-1],
如果从1开始这就有-1了,于是从2开始,先在root[1]加个0进去。
YYR学长的平方分割大法:
我们先来考虑,若没有添加操作显然可以将所的后缀异或和建成持久化Trie树来做。
那如果添加了 k个元素呢?
查询时可以将 x异或上添加的 k个元素,再到Trie中查询。实际上也就是每添加 lim 个元素后,重新构建数据结构。
假设查询和添加操作次数相近,那么这道题样做的话
建树总复杂度 O(N * 位数 * (N/ lim )) ,
查询总复杂度 O(N * (lim + 位数 )) 。
利用前面的方法,可以得到 lim 取 sqrt (N * (N * 位数 ) 时较优。
复杂度O(N * sqrt sqrt (N * (N * 位数 ))
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int P=30;
const int N=600005;
struct node
{
int ch[2],size;
}tr[N*35];
int n,m,tail=0,a[N],root[N];
void insert(int pre,int &now,int val,int p)
{
now=++tail; tr[now]=tr[pre]; tr[now].size++;
if(p==-1) return;
int opt=(val>>p)&1;
insert(tr[pre].ch[opt],tr[now].ch[opt],val,p-1);
}
int query(int pre,int nd,int val,int p,int ans)
{
if(p==-1) return ans;
int opt=(val>>p)&1; opt=opt^1;
int ns=tr[tr[nd].ch[opt]].size; int ps=tr[tr[pre].ch[opt]].size;
if(ns-ps>0) return query(tr[pre].ch[opt],tr[nd].ch[opt],val,p-1,ans+(1<<p));
else return query(tr[pre].ch[opt^1],tr[nd].ch[opt^1],val,p-1,ans);
}
int main()
{
scanf("%d%d",&n,&m); n++;
for(int i=2;i<=n;i++) scanf("%d",&a[i]); a[0]=a[1]=0;
for(int i=2;i<=n;i++) a[i]=a[i-1]^a[i];
root[0]=0; tr[0].ch[0]=tr[0].ch[1]=tr[0].size=0;
for(int i=1;i<=n;i++) insert(root[i-1],root[i],a[i],P);
while(m--)
{
char opt[5]; int x,l,r; scanf("%s",opt);
if(opt[0]=='A')
{
n++; scanf("%d",&a[n]); a[n]^=a[n-1];
insert(root[n-1],root[n],a[n],P);
}
else
{
scanf("%d%d%d",&l,&r,&x);
x=x^a[n];
printf("%d\n",query(root[l-1],root[r],x,P,0));
}
}
return 0;
}