====== 可持久化字典树 ======
===== 算法简介 =====
一种可以维护历史版本的字典树,实现方面与可持久化线段树类似,主要用于维护区间异或和。
时间复杂度和空间复杂度均为 $O\left(m\log n\right)$。
===== 算法例题 =====
[[https://www.luogu.com.cn/problem/P4735|洛谷p4735]]
==== 题意 ====
给定序列 $a_1,a_2\cdots a_n$,支持下述操作:
- 在序列末尾添加一个数 $x$
- 给定 $l,r,x$,询问 $\max_{l \le p\le r} x\oplus(a_p\oplus a_{p+1}\oplus \cdots \oplus a_N)$,其中 $N$ 表示当前序列长度
==== 题解 ====
设 $S_i=a_1\oplus a_2\oplus \cdots \oplus a_i$,于是询问操作变为 $\max_{l-1\le p\le r-1} x\oplus S_N\oplus S_p$。
考虑对序列 ${S_0,S_1,S_2\cdots S_n}$ 建立可持久化字典树,修改操作等价于在最后版本基础上插入新元素。
查询操作用类似主席树的方法查询区间中每个数与 $x\oplus S_N$ 异或的最大值即可。
const int MAXN=3e5+5,MAXM=24;
int root[MAXN<<1],ch[MAXN*50][2],val[MAXN*50],cnt;
void Insert(int &k,int p,int pos,int v){
k=++cnt;
val[k]=val[p]+1;
if(pos<0)return;
int dir=(v>>pos)&1;
ch[k][!dir]=ch[p][!dir];
Insert(ch[k][dir],ch[p][dir],pos-1,v);
}
int query(int k1,int k2,int v){
int ans=0,pos=MAXM-1;
while(~pos){
int dir=(v>>pos)&1;
if(val[ch[k2][!dir]]-val[ch[k1][!dir]])
ans|=(1<
===== 算法练习 =====
==== 习题一 ====
[[https://www.luogu.com.cn/problem/P4098|洛谷p4098]]
=== 题意 ===
给定 $n$ 个互异的数。求 $a_i\oplus a_j$ 的最大值,其中 $i,j\in [l,r]$ 且 $a_i$ 为 $a_l,a_{l+1}\cdots a_r$ 的次大值。
=== 题解 ===
枚举每个次大值,显然次大值确定时区间尽可能向两边拓展可以取到最优解。
考虑对 $a_i$ 进行排序,然后通过双向链表确定最优区间(至多存在两个)。区间查询通过可持久化字典即可完成。
时空间复杂度 $O(n\log v)$。
const int MAXN=5e4+5,MAXM=30;
int root[MAXN],ch[MAXN*32][2],val[MAXN*32],cnt;
void Insert(int &k,int p,int pos,int v){
k=++cnt;
val[k]=val[p]+1;
if(pos<0)return;
int dir=(v>>pos)&1;
ch[k][!dir]=ch[p][!dir];
Insert(ch[k][dir],ch[p][dir],pos-1,v);
}
int query(int lef,int rig,int v){
int ans=0,pos=MAXM-1,k1=root[lef-1],k2=root[rig];
while(~pos){
int dir=(v>>pos)&1;
if(val[ch[k2][!dir]]-val[ch[k1][!dir]])
ans|=(1< a[MAXN];
int main()
{
int n=read_int(),head=0,tail=n+1;
nxt[head]=1,pre[tail]=n;
_rep(i,1,n){
nxt[i]=i+1,pre[i]=i-1;
a[i]=make_pair(read_int(),i);
Insert(root[i],root[i-1],MAXM-1,a[i].first);
}
sort(a+1,a+n+1);
int ans=0;
_rep(i,1,n){
int l=pre[a[i].second],r=nxt[a[i].second];
nxt[l]=r,pre[r]=l;
if(l!=head)ans=max(ans,query(pre[l]+1,r-1,a[i].first));
if(r!=tail)ans=max(ans,query(l+1,nxt[r]-1,a[i].first));
}
enter(ans);
return 0;
}
==== 习题二 ====
[[https://www.luogu.com.cn/problem/P5283|洛谷p5283]]
=== 题意 ===
给定 $n$ 个互异的数,求前 $k$ 大的区间异或和。
=== 题解 ===
前缀异或和处理,问题转化为求前 $k$ 大的点对异或和。
考虑用优先队列维护每个固定点 $i$ 对配对区间 $[0,i-1]$ 的最佳答案,设 $i$ 的最大答案位置为 $k_i$。则全局最大答案一定为某个 $(k_i,i)$。
弹出全局最大答案,固定点 $i$ 的第二大答案位置一定在区间 $[0,k_i-1]$ 和 $[k_i+1,i-1]$,将其加入优先队列。
重复 $k$ 次即可得到全局前 $k$ 大答案。
const int MAXN=5e5+5,MAXM=32;
int root[MAXN],ch[MAXN*35][2],val[MAXN*35],idx[MAXN*35],cnt;
void Insert(int &k,int p,int id,LL v,int pos){
k=++cnt;
val[k]=val[p]+1;
if(pos<0){
idx[k]=id;
return;
}
int dir=(v>>pos)&1;
ch[k][!dir]=ch[p][!dir];
Insert(ch[k][dir],ch[p][dir],id,v,pos-1);
}
int query(int lef,int rig,LL v){
int pos=MAXM-1,k1=lef?root[lef-1]:0,k2=root[rig];
while(~pos){
int dir=(v>>pos)&1;
if(val[ch[k2][!dir]]-val[ch[k1][!dir]])
k1=ch[k1][!dir],k2=ch[k2][!dir];
else
k1=ch[k1][dir],k2=ch[k2][dir];
pos--;
}
return idx[k2];
}
LL a[MAXN];
struct Node{
int ql,qr,x,nxt;
LL v;
bool operator < (const Node &b)const{
return vql=ql,this->qr=qr,this->x=x;
nxt=query(ql,qr,a[x]);
v=a[x]^a[nxt];
}
};
priority_queue q;
int main()
{
int n=read_int(),k=read_int();
Insert(root[0],root[0],0,0,MAXM-1);
_rep(i,1,n)a[i]=read_LL()^a[i-1],Insert(root[i],root[i-1],i,a[i],MAXM-1);
_rep(i,1,n)q.push(Node(0,i-1,i));
LL ans=0;
while(k--){
Node temp=q.top();q.pop();
ans+=temp.v;
if(temp.nxt>temp.ql)q.push(Node(temp.ql,temp.nxt-1,temp.x));
if(temp.nxt
==== 习题三 ====
[[https://www.luogu.com.cn/problem/P5795|洛谷p5795]]
=== 题意 ===
给定一个长度为 $n$ 的序列 $X$ 和长度为 $m$ 的序列 $Y$,定义矩阵元素 $A_{i,j}=X_i\oplus Y_j$,接下来 $q$ 个询问。
每次询问 $A_{i,j}(l_1\le i\le r_1,l_2\le j\le r_2)$ 第 $k$ 大。数据保证 $n\le 1000,m\le 300000,q\le 500$。
=== 题解 ===
对序列 $Y$ 建字典树,差分维护区间结点个数。每次询问时同时对序列 $X_i(l_1\le r_1)$ 跳字典树。
考虑按位贪心,先查询异或结果为 $1$ 的结点个数,根据结点个数考虑跳边方向。时间复杂度 $O((nq+m)\log v)$。
const int MAXN=1e3+5,MAXM=3e5+5,MAXL=31;
struct Node{
int ch[2],sz;
}node[MAXM*40];
int root[MAXM],node_cnt;
void Insert(int &k,int p,int v){
int pos=k=++node_cnt;
for(int i=MAXL-1;~i;i--){
int dir=(v>>i)&1;
node[pos].ch[!dir]=node[p].ch[!dir];
node[pos].ch[dir]=++node_cnt;
pos=node[pos].ch[dir];
p=node[p].ch[dir];
node[pos].sz=node[p].sz+1;
}
}
int x[MAXN],y[MAXM],cur_pos[MAXN][2];
int query(int l1,int r1,int l2,int r2,int rk){
int ans=0;
_rep(i,l1,r1){
cur_pos[i][0]=root[r2];
cur_pos[i][1]=root[l2-1];
}
for(int i=MAXL-1;~i;i--){
int cnt=0;
_rep(j,l1,r1){
int dir=((x[j]>>i)&1)^1;
cnt+=node[node[cur_pos[j][0]].ch[dir]].sz-node[node[cur_pos[j][1]].ch[dir]].sz;
}
int flag=cnt>=rk;
if(flag)
ans|=1<>i)&1)^flag;
cur_pos[j][0]=node[cur_pos[j][0]].ch[dir];
cur_pos[j][1]=node[cur_pos[j][1]].ch[dir];
}
}
return ans;
}
int main()
{
int n=read_int(),m=read_int();
_rep(i,1,n)x[i]=read_int();
_rep(i,1,m){
y[i]=read_int();
Insert(root[i],root[i-1],y[i]);
}
int q=read_int();
while(q--){
int l1=read_int(),r1=read_int(),l2=read_int(),r2=read_int();
enter(query(l1,r1,l2,r2,read_int()));
}
return 0;
}