传送门
解析:
看到标题的
d
a
l
a
o
dalao
dalao先不要急着锤我。。。
这道题的二进制拆分和
01
T
r
i
e
01Trie
01Trie不能混在一起,不要急着说
01
T
r
i
e
01Trie
01Trie就是二进制拆分。。。
思路:
这道题可以说是非常好的一道数据结构。
我相信应该没有人会去想计算几何。(那这出题人得有多善良)
先看操作1,要求在末尾插入一个数,这个与数据结构没有什么关系,我们可以直接在数据结构外建一个数组存。
操作2,询问区间和,这个就是维护前缀和,还是不能帮助我们确定使用什么数据结构。
操作3,全局
x
o
r
xor
xor。
空气凝固。。。这个除了
01
T
r
i
e
01Trie
01Trie还能怎么做?
操作4,全局排序。。。 01 T r i e 01Trie 01Trie内部的数字本来就是排了序的。我们只需要记录之前 x o r xor xor了一个什么东西就能够确定内部具体的顺序,在每次排序的时候将相数组里面的数全部插入 T r i e Trie Trie树里面。
那这道题就是 01 T r i e 01Trie 01Trie咕咕咕了。
好,最(被和谐)的地方到了。
对于一个有全局 x o r xor xor的题,我们要怎么维护前缀和?
这里就是刚才提到的二进制拆分的作用了。
对于所有的 x o r xor xor我们都不直接作用在数组和 T r i e Trie Trie树上,而是用一个标记记录当前应该要 x o r xor xor上一个什么东西。
而前缀和,我们将所有数的二进制表示拆开,分位统计前缀和(统计该位出现次数)。记录在 s u m sum sum数组里面。那么 s u m [ x ] [ i ] sum[x][i] sum[x][i]表示前 x x x位中在第 i i i位出现了多少个 1 1 1,那么出现的 0 0 0的个数就是 x − s u m [ x ] [ i ] x-sum[x][i] x−sum[x][i]。这两个个数都是在异或上 x o r t a g xortag xortag之前而言的
每次询问一个位置,我们就看一下 x o r t a g xortag xortag这一位是否是1,而采取是统计 s u m [ x ] [ i ] sum[x][i] sum[x][i]还是 x − s u m [ x ] [ i ] x-sum[x][i] x−sum[x][i]。
同理,在
T
r
i
e
Trie
Trie树中,我们统计每个节点子树出现各个位的次数。在记录前缀和的时候根据子树大小调整就行了。
不要忘了在叶子节点的时候,只能统计该统计的部分,这个需要单独处理一下。
假装被和谐掉了的复杂度分析
根据我们刚才的叙述,我们可以得到一个不太对的复杂度。
单次
x
o
r
xor
xor的复杂度
O
(
1
)
O(1)
O(1)
T
r
i
e
Trie
Trie树单次插入的复杂度
O
(
l
o
g
2
A
i
)
O(log^2A_i)
O(log2Ai)(插入+二进制前缀和)
T
r
i
e
Trie
Trie树单次询问的复杂度
O
(
l
o
g
2
A
i
)
O(log^2A_i)
O(log2Ai)
数组单次插入
O
(
l
o
g
A
i
)
O(logA_i)
O(logAi)
数组单次询问复杂度
O
(
l
o
g
A
i
)
O(logA_i)
O(logAi)
单次排序复杂度 O ( 数 组 中 数 个 数 × T r i e 的 插 入 复 杂 度 ) O(数组中数个数\times Trie的插入复杂度) O(数组中数个数×Trie的插入复杂度)。
而总复杂度可以近似认为是 O ( ( n + m ) l o g n ∗ l o g A ) O((n+m)logn*logA) O((n+m)logn∗logA)
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const
inline
ll getint(){
re ll num;
re char c;
while(!isdigit(c=gc()));num=c^48;
while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
return num;
}
inline
void outint(ll a){
static char ch[23];
if(0==a)pc('0');
while(a)ch[++ch[0]]=a-a/10*10,a/=10;
while(ch[0])pc(ch[ch[0]--]^48);
}
cs int N=100005;
int xortag;
struct _01TRIE{
#define root 0
int son[N*30][2];
int siz[N*30];
int sum[N*30][30];
int tot;
int tag;
_01TRIE(){tot=0;}
void insert(int a){
int now=root;
for(int re i=29;~i;--i){
bool f=a&(1<<i);
if(!son[now][f])son[now][f]=++tot;
now=son[now][f];
++siz[now];
for(int re j=29;~j;--j){
if(a&(1<<j))++sum[now][j];
}
}
}
ll querysubtree(int pos){
ll ans=0;
for(int re i=29;~i;--i)
if(xortag&(1<<i))ans+=(1ll*siz[pos]-sum[pos][i])<<i;
else ans+=sum[pos][i]*1ll<<i;
return ans;
}
ll querysum(int x){
if(x==0)return 0;
ll ans=0;
int now=root;
for(int re i=29;~i;--i){
bool f=0;
if(tag&(1<<i))f^=1;
if(x<=siz[son[now][f]])now=son[now][f];
else{
ans+=querysubtree(son[now][f]);
x-=siz[son[now][f]];
now=son[now][!f];
}
}
ans+=querysubtree(now)/siz[now]*x;
return ans;
}
int size(){
return siz[son[root][0]]+siz[son[root][1]];
}
#undef root
}Trie;
struct array{
int tot;
int sum[N][30];
int h[N];
void insert(int a){
h[++tot]=(a^=xortag);
for(int re i=29;~i;--i)
sum[tot][i]=sum[tot-1][i]+((a>>i)&1);
}
ll querysum(int pos){
ll ans=0;
for(int re i=29;~i;--i)
if(xortag&(1<<i))ans+=(1ll*pos-sum[pos][i])<<i;
else ans+=sum[pos][i]*1ll<<i;
return ans;
}
void sort(){
while(tot)Trie.insert(h[tot--]);
Trie.tag=xortag;
}
}Array;
inline
ll query(int pos){
if(pos>Trie.size())return Trie.querysum(Trie.size())+Array.querysum(pos-Trie.size());
return Trie.querysum(pos);
}
int n,m;
signed main(){
n=getint();
for(int re i=1;i<=n;++i){
int a=getint();
Array.insert(a);
}
m=getint();
while(m--){
int op=getint();
switch(op){
case 1:{
int x=getint();
Array.insert(x);
break;
}
case 2:{
int l=getint(),r=getint();
outint(query(r)-query(l-1));pc('\n');
break;
}
case 3:{
xortag^=getint();
break;
}
case 4:{
Array.sort();
break;
}
}
}
return 0;
}