可持久化 trie/并查集 学习笔记
算是跟主席树差不多的一点杂项?如果学了新的可能会更新。
0x00 前言
我们已经在这里介绍了主席树与其“可持久化”的思想,而本文主要内容算是可持久化在一些其他数据结构上的应用。
0x01 可持久化 trie
例题:
题意:两种操作:1.在数组最后插入一个数;2.求左端点在 [l,r] 范围内的后缀异或和与 x 异或的最大值。\(n,m\le3\times10^5,0\le a_i\le10^7\)。
做法:
定义 \(s_i=a_1\oplus a_2\oplus\ldots a_i\),答案等价于查 \(s_p\oplus(s_n\oplus x),p\in[l-1,r-1]\) 的最大值。后面的看成常数,想到 trie 树。对 trie 树可持久化即可。
实现:
有一个略嫌麻烦的地方:在转化题意时我们把区间变成了 [l-1,r-1],又因为可持久化前缀减的原因需要找 l-2,可能会越界。实现时建议最开始在最前面插入一个 0,整体后移一位。
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct Trie{
int T,root[600005],son[13500005][2],cnt[13500005];
void insert(int p,int q,int val){
for(int i=24,o;i>=0;i--){
cnt[p]=cnt[q]+1,o=(val>>i)&1ll;
if(!son[p][o])son[p][o]=++T;
son[p][o^1]=son[q][o^1];
p=son[p][o],q=son[q][o];
}
cnt[p]=cnt[q]+1;
}
int ask(int p,int q,int val){
int res=0;
for(int i=24;i>=0;i--){
int o=(val>>i)&1ll;
if(cnt[son[p][o^1]]-cnt[son[q][o^1]]>0)res+=(1ll<<i),p=son[p][o^1],q=son[q][o^1];
else p=son[p][o],q=son[q][o];
}
return res;
}
}Tr;
int a[600005],s[600005];char op[10];
signed main(){
int n=read()+1,m=read();
a[1]=s[1]=0;for(int i=2;i<=n;i++)a[i]=read(),s[i]=s[i-1]^a[i];
Tr.root[0]=++Tr.T;Tr.insert(Tr.root[0],0,s[0]);
for(int i=1;i<=n;i++)Tr.root[i]=++Tr.T,Tr.insert(Tr.root[i],Tr.root[i-1],s[i]);
while(m--){
scanf("%s",op);int l,r,x;
if(op[0]=='A')x=read(),s[n+1]=s[n]^x,n++,Tr.root[n]=++Tr.T,Tr.insert(Tr.root[n],Tr.root[n-1],s[n]);
else l=read()+1,r=read()+1,x=read(),printf("%lld\n",Tr.ask(Tr.root[r-1],Tr.root[l-2],s[n]^x));
}
return 0;
}
应用:
lyd 写法,不行()
0x02 可持久化并查集
例题:
题意:三种操作:1.合并 a,b 所在集合;2.回到第 k 次操作之后的状态;3.询问 a,b 是否属于同一集合。\(n\le10^5,m\le2\times10^5\)。(ACwing 上强制在线)
做法:
显然需要可持久化 fa 数组,但是由于路径压缩是均摊的 \(\text{O}(\log n)\),我们需要单次严格 \(\log n\) 的做法。有两种方案:按树高合并/按大小合并。证明如下:
1.树高:定义以 r 为根的树 T 的树高 \(h_r\) 等于离 r 最远的点的距离。假如有两棵以 a,b 为根的树需要合并(满足 \(h_a\ge h_b\)),我们会把 b 接到 a 下面。容易发现只有当 \(h_a=h_b\) 时 \(h_a\) 才会增大 1,其他时候不变。定义 \(f(h)\) 表示树高为 h 的树至少有几个节点,显然 \(f(h+1)=2f(h),f(1)=1\),故 \(h\le\log n\)。
2.大小:同理,这里不再赘述。
find 和 merge 为 \(\text{O}(\log^2 n)\),回溯为 \(\text{O}(1)\)。
实现:
写的子树大小的版本。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,m,ver,_[200005];
struct segtree{
#define ls c[p].lc
#define rs c[p].rc
#define lson l,mid,ls
#define rson mid+1,r,rs
struct Node{
int c,lc,rc;
}c[7000005];
int T,root[200005];
int build(int l,int r){
int p=++T;
if(l==r){c[p].c=_[l];return p;}
int mid=(l+r)>>1;
ls=build(l,mid),rs=build(mid+1,r);
return p;
}
int update(int l,int r,int q,int x,int k){
int p=++T;c[p]=c[q];
if(l==r){c[p].c=k;return p;}
int mid=(l+r)>>1;
if(x<=mid)ls=update(l,mid,c[q].lc,x,k);
else rs=update(mid+1,r,c[q].rc,x,k);
return p;
}
int query(int l,int r,int p,int x){
if(l==r)return c[p].c;
int mid=(l+r)>>1;
if(x<=mid)return query(lson,x);
else return query(rson,x);
}
#undef ls
#undef rs
#undef lson
#undef rson
}TrFa,TrSiz;
int find(int x){
while(1){
int fa=TrFa.query(1,n,TrFa.root[ver],x);
if(fa!=x)x=fa;else break;
}
return x;
}
void merge(int x,int y){
int a=find(x),b=find(y);if(a==b)return;
int sza=TrSiz.query(1,n,TrSiz.root[ver],a),szb=TrSiz.query(1,n,TrSiz.root[ver],b);
if(sza>szb)swap(x,y),swap(a,b),swap(sza,szb);
TrFa.root[ver]=TrFa.update(1,n,TrFa.root[ver],a,b);
TrSiz.root[ver]=TrSiz.update(1,n,TrSiz.root[ver],b,sza+szb);
}
void recall(int k){
TrFa.root[ver]=TrFa.root[k];
TrSiz.root[ver]=TrSiz.root[k];
}
int ask(int x,int y){
int a=find(x),b=find(y);
return (a==b);
}
signed main(){
n=read(),m=read();
for(int i=1;i<=n;i++)_[i]=i;
TrFa.root[0]=TrFa.build(1,n);
for(int i=1;i<=n;i++)_[i]=1;
TrSiz.root[0]=TrSiz.build(1,n);
for(ver=1;ver<=m;ver++){
TrFa.root[ver]=TrFa.root[ver-1];
TrSiz.root[ver]=TrSiz.root[ver-1];
int op=read(),a,b;
if(op==1)a=read(),b=read(),merge(a,b);
else if(op==2)a=read(),recall(a);
else a=read(),b=read(),printf("%lld\n",ask(a,b));
}
return 0;
}
应用:
暂时没发现?
0x03 总结
感觉这些可持久化的用途并不算太广?