可持久化 trie/并查集 学习笔记

算是跟主席树差不多的一点杂项?如果学了新的可能会更新。

0x00 前言

我们已经在这里介绍了主席树与其“可持久化”的思想,而本文主要内容算是可持久化在一些其他数据结构上的应用。

0x01 可持久化 trie

例题:

P4735 最大异或和

题意:两种操作: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,整体后移一位。

P4735 最大异或和

#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;
}

应用:

SP11444 MAXOR - MAXOR

lyd 写法,不行()

0x02 可持久化并查集

例题:

P3402 可持久化并查集

题意:三种操作: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)\)

实现:

写的子树大小的版本。

P3402 可持久化并查集

#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 总结

感觉这些可持久化的用途并不算太广?

posted @ 2023-04-23 20:12  xx019  阅读(22)  评论(0编辑  收藏  举报