我的坟头应该开满玫瑰吗?|

YYYmoon

园龄:1年粉丝:20关注:41

可持久化数据结构

可持久化数据结构呢,就是说这些数据结构,它们都非常持久(

其实就是可以访问和修改历史版本的信息

可持久化线段树

可持久化权值线段树就是主席树

如果你还不太了解,可以看看

当然还有更普遍的可持久化线段树——支持区间修改的。考虑pushdown会影响下方历史版本的线段树信息,自然想到标记永久化。就是不用pushdown或pushup,每次把修改操作的tag打到一个完整覆盖的区间上,并且在其途径的节点上直接修改sum。查询的时候返回被完整覆盖到的区间 t[id].sum+途径的tag之和*(t[id].r-t[id].l+1) 的和。

Q:树套树不是单点修改吗(比区间修改要求更少)?为什么不可以用标记永久化?

A:很显然标记永久化处理的是维护区间的线段树,且每次修改都新建一个版本。而树套树由于询问要求,必须建权值线段树,这样对于修改来说,就相当于在中间的版本进行操作,还会影响到后面的版本,就必须用树套树了。

[可持久化线段树区间修改模板]To the moon
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
int n,m,a[maxn],rt[maxn],cnt,now;
struct node{
	int lc,rc;
	ll v,tg;
}t[maxn<<6];
void build(int &x,int l,int r){
	x=++cnt;
	if(l==r) return t[x].v=a[l],void();
	int mid=(l+r)>>1;
	build(t[x].lc,l,mid);
	build(t[x].rc,mid+1,r);
	t[x].v=t[t[x].lc].v+t[t[x].rc].v;
}
void add(int &x,int y,int l,int r,int vl,int vr,int val){
	x=++cnt,t[x]=t[y];
	t[x].v+=1ll*val*(min(r,vr)-max(l,vl)+1);//
	if(vl<=l&&r<=vr) return t[x].tg+=val,void();
	int mid=(l+r)>>1;
	if(vl<=mid) add(t[x].lc,t[y].lc,l,mid,vl,vr,val);
	if(vr>mid) add(t[x].rc,t[y].rc,mid+1,r,vl,vr,val);
}
ll query(int x,int l,int r,int vl,int vr,ll tg){
	if(vl<=l&&r<=vr) return t[x].v+tg*(r-l+1);//
	int mid=(l+r)>>1; tg+=t[x].tg; ll res=0;
	if(vl<=mid) res=query(t[x].lc,l,mid,vl,vr,tg);
	if(vr>mid) res+=query(t[x].rc,mid+1,r,vl,vr,tg); 
	return res;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	build(rt[0],1,n);
	while(m--){
		char c; int l,r,t;
		scanf(" %c",&c);
		if(c=='C'){
			scanf("%d%d%d",&l,&r,&t);
			add(rt[now+1],rt[now],1,n,l,r,t),now++;
		}
		else if(c=='Q'){
			scanf("%d%d",&l,&r);
			printf("%lld\n",query(rt[now],1,n,l,r,0));
		}
		else if(c=='H'){
			scanf("%d%d%d",&l,&r,&t);
			printf("%lld\n",query(rt[t],1,n,l,r,0));
		} 
		else{
			scanf("%d",&t);
			now=t;
		}
	}
	return 0;
} 

神秘例题:七彩树

根据可持久化线段树的套路,一般都是对于一维建可持久化线段树,另一位在线段树内部维护。

本题,我们发现如果按照数颜色问题最常见套路,记录该颜色上一次出现的位置去做,会把我们二维的题变成三维的,完全不可行。考虑更直接的思路,对当前颜色(上一次出现的位置+1)到(当前位置)的不同颜色个数要整体+1,用树上差分维护。由于树上差分的存在,那么我们就只能以深度范围为轴建立可持久化线段树,每一棵线段树维护当前深度的点对整棵树dfs序上对应点的贡献。

七彩树
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+5;
int T,n,m,h[maxn],to[maxn],nxt[maxn],cnt,f[maxn][21],dep[maxn],dfn[maxn],num;
int lst,id[maxn],tot,siz[maxn],a[maxn],rt[maxn],idx[maxn],clo;
struct node{
	int lc,rc,v;
}t[maxn<<5];
set<int>s[maxn];
map<int,int>mp;
void addedge(int u,int v){
	nxt[++cnt]=h[u];
	to[cnt]=v;
	h[u]=cnt;
}
void init(){
	for(int i=1;i<=n;i++){
		h[i]=to[i]=nxt[i]=rt[i]=0;
		s[i].clear();
		for(int j=0;j<=20;j++) f[i][j]=0;
	}
	for(int i=1;i<=tot;i++) t[i].lc=t[i].rc=t[i].v=0;
	mp.clear();
	lst=cnt=num=tot=clo=0;
} 
void pre(int x,int fa){
	f[x][0]=fa,siz[x]=1;
	dep[x]=dep[fa]+1;
	dfn[x]=++num,idx[num]=x;
	for(int i=1;i<=20;i++)
		f[x][i]=f[f[x][i-1]][i-1];
	for(int i=h[x];i;i=nxt[i]){
		int y=to[i];
		if(y!=fa) pre(y,x),siz[x]+=siz[y];
	} 
} 
int lca(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=20;i>=0;i--){
		if(dep[f[x][i]]>=dep[y]) x=f[x][i];
	}
	if(x==y) return x;
	for(int i=20;i>=0;i--){
		if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
	}
	return f[x][0];
} 
bool cmp(int x,int y){
	return dep[x]<dep[y];
}
void add(int &x,int l,int r,int pos,int val){
	if(!x) x=++tot; t[x].v+=val;
	if(l==r) return ;
	int mid=(l+r)>>1;
	if(pos<=mid) add(t[x].lc,l,mid,pos,val);
	else add(t[x].rc,mid+1,r,pos,val); 
}
int merge(int x,int y){
	if(!x||!y) return x+y;
	t[x].v+=t[y].v;
	t[x].lc=merge(t[x].lc,t[y].lc);
	t[x].rc=merge(t[x].rc,t[y].rc);
	return x;
}
int query(int x,int y,int l,int r,int vl,int vr){
	if(vl<=l&&r<=vr) return t[x].v-t[y].v;
	int mid=(l+r)>>1,res=0;
	if(vl<=mid) res=query(t[x].lc,t[y].lc,l,mid,vl,vr);
	if(vr>mid) res+=query(t[x].rc,t[y].rc,mid+1,r,vl,vr);
	return res; 
}
int main(){
	scanf("%d",&T);
	while(T--){
		init();
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			if(!mp[a[i]]) mp[a[i]]=++clo;
			a[i]=mp[a[i]];
		}
		for(int i=2,fa;i<=n;i++){
			scanf("%d",&fa);
			addedge(fa,i);
		}
		pre(1,0);
		for(int i=1;i<=n;i++) id[i]=i;
		sort(id+1,id+n+1,cmp);
		int now=0;
		for(int i=1;i<=n;i++){
			if(dep[id[i]]!=dep[id[i-1]]) now++;
			int c=a[id[i]],x=id[i],lc=0;
			if(s[c].size()){
				auto it=s[c].lower_bound(dfn[x]);
				if(it!=s[c].end()){
	//				printf("%d\n",(*it));
					int p=lca(idx[(*it)],x);
					lc=p;
				}
				if(it!=s[c].begin()){
					it--;
	//				printf("%d\n",(*it));
					int p=lca(idx[(*it)],x);
					if(dep[lc]<dep[p]) lc=p;
				}	
			}
			s[c].insert(dfn[x]);
			if(lc) add(rt[now],1,n,dfn[lc],-1);
			add(rt[now],1,n,dfn[x],1);
//			printf("do: %d %d %d\n",now,x,lc);
		}
		for(int i=1;i<=now;i++) rt[i]=merge(rt[i],rt[i-1]);
		while(m--){
			int x,d;
			scanf("%d%d",&x,&d);
			x^=lst,d^=lst,d=min(d,now-dep[x]);
//			printf("%d %d %d %d %d\n",x,d,dfn[x],siz[x],dep[x]);
			printf("%d\n",lst=query(rt[dep[x]+d],rt[dep[x]-1],1,n,dfn[x],dfn[x]+siz[x]-1));
		}
	}
	return 0;
}

可持久化平衡树

一般使用的都是可持久化FHQ-treap,代码短,常数小,操作简单,容易理解(splay因存在旋转操作,不能可持久化

(实际上是因为我平衡树只会FHQ-treap

前置:FHQ_treap,即无旋treap

treap是维护了一个二元组,其中val为权值,prio(priority)为一个随机的优先级。其中val满足二叉搜索树性质(中序遍历结果是val从小到大的排序),prio满足(小根或大根)堆的性质。

实际上,treap就是依靠这个随机化赋予的权值,打乱节点的插入顺序,保证树的形态不会太差,高度大概在logn左右,从而保证各种操作的期望复杂度是O(logn)

split(分裂) :两种分裂方式,按排名或按值。p表示当前子树根节点,v表示以v为分界线分割树,x,y表示分裂之后左右子树的根。注意,当根为0时,一定要清空x,y,否则死循环!!!

merge(合并) :实际上只需要按照prio的值决定父子节点关系,然后类似于线段树合并去做就行。注意这里合并的时候一定要按照权值顺序合并,因为我们在merge函数里只考虑了优先级,没有考虑权值的问题。

添加值操作,实际上就是按值分裂开,然后再把新建的点和它们合并到一起(跟拼拼图似的是吧

删除值操作类似,把区间按值拆开,把中间一段去掉再合并就行

查询排名为k的值,直接按二叉搜索树的性质递归即可

前后继和排名以此类推

空间O(n),时间O(nlogn)

[模板]普通平衡树
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
int root,num,n;
struct tree{
	int l,r,val,tmp,size;
}t[maxn];//val权值,tmp随机值
void newnode(int &x,int v){
	x=++num;
	t[x].val=v;
	t[x].tmp=rand(); 
	t[x].size=1;
}
void pushup(int p){
	t[p].size=t[t[p].l].size+t[t[p].r].size+1;
}
void split(int p,int v,int &x,int &y){//分裂维护顺序 
	if(!p){//注意清空! 
		x=y=0;
		return ;
	}
	if(t[p].val<=v){
		x=p;
		split(t[x].r,v,t[x].r,y);
		pushup(x);
	}
	else{
		y=p;
		split(t[y].l,v,x,t[y].l);
		pushup(y);
	}
}
int merge(int x,int y){
	if(!x||!y) return x+y;
	if(t[x].tmp<t[y].tmp){//依靠随机数 
		t[x].r=merge(t[x].r,y);
		pushup(x); return x;
	}
	else{
		t[y].l=merge(x,t[y].l);
		pushup(y); return y;
	}
}
void insert(int v){
	int x,y,z;
	split(root,v,x,y);
	newnode(z,v);
	root=merge(merge(x,z),y);
}
void del(int v){
	int x,y,z;
	split(root,v,x,z);
	split(x,v-1,x,y);
	y=merge(t[y].l,t[y].r);
	root=merge(merge(x,y),z);
}
int qnum(int v){
	int x,y;
	split(root,v-1,x,y);
	int ans=t[x].size+1;
	root=merge(x,y);
	return ans;
}
int query(int root,int v){
	if(v==t[t[root].l].size+1) return t[root].val;
	else if(v<=t[t[root].l].size) return query(t[root].l,v);
	else return query(t[root].r,v-t[t[root].l].size-1); 
}
int pre(int v){
	int x,y,s,ans;
	split(root,v-1,x,y);
	s=t[x].size;
	ans=query(x,s);
	root=merge(x,y);
	return ans;
}
int nxt(int v){
	int x,y,ans;
	split(root,v,x,y);
	ans=query(y,1);
	root=merge(x,y);
	return ans;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int tmp,v;
		scanf("%d%d",&tmp,&v);
		if(tmp==1) insert(v);
		else if(tmp==2) del(v);
		else if(tmp==3) printf("%d\n",qnum(v));
		else if(tmp==4) printf("%d\n",query(root,v));
		else if(tmp==5) printf("%d\n",pre(v));
		else printf("%d\n",nxt(v));
	}
	return 0;
}

可持久化普通平衡树

和普通平衡树的区别其实有且仅有每次分裂和合并时不覆盖(直接使用)之前的节点,而是新建节点。因为这样才能保证历史信息一直存在,方便后面调用。

注意空间要开nlogn!!!因为每次操作都新建节点!

可持久化普通平衡树
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+5;
const int inf=(1ll<<31)-1;
int n,rt[maxn],num;
struct node{
	int lc,rc,siz,v,prio;
}t[maxn<<6];
int newnode(int v){
	t[++num].siz=1;
	t[num].v=v;
	t[num].prio=rand();
	return num;
} 
void pushup(int x){
	t[x].siz=t[t[x].lc].siz+t[t[x].rc].siz+1;
}
void split(int p,int v,int &x,int &y){
	if(!p){
		x=y=0; return ;
	}
	if(t[p].v<=v){
		x=newnode(0); t[x]=t[p];
		split(t[x].rc,v,t[x].rc,y);
		pushup(x);
	}
	else{
		y=newnode(0); t[y]=t[p];
		split(t[y].lc,v,x,t[y].lc);
		pushup(y);
	}
}
int merge(int x,int y){
	if(!x||!y) return x+y;
	if(t[x].prio<t[y].prio){
		int p=newnode(0); t[p]=t[x];
		t[p].rc=merge(t[p].rc,y);
		pushup(p); return p;
	}
	else{
		int p=newnode(0); t[p]=t[y];
		t[p].lc=merge(x,t[p].lc);
		pushup(p); return p;
	}
}
int kth(int x,int v){
	if(t[t[x].lc].siz+1==v) return t[x].v;
	if(v<=t[t[x].lc].siz) return kth(t[x].lc,v);//搞笑,这儿写错了、、 
	else return kth(t[x].rc,v-t[t[x].lc].siz-1);
}
int read(){
	char ch=getchar(); int f=1,x=0;
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}
void write(int x){
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x/10);
	putchar('0'+x%10);
}
int main(){
	srand(time(0));
	n=read();
	for(int i=1;i<=n;i++){
		int p=read(),opt=read(),v=read(),x,y,z;
		rt[i]=rt[p];
		if(opt==1){//insert
			split(rt[i],v,x,y);
			rt[i]=merge(x,merge(newnode(v),y));
		}
		else if(opt==2){//del
			split(rt[i],v-1,x,y);
			split(y,v,z,y);
			z=merge(t[z].lc,t[z].rc);
			rt[i]=merge(x,merge(z,y));
		}
		else{
			if(opt==3){//qnum
				split(rt[i],v-1,x,y);
				write(t[x].siz+1);
				rt[i]=merge(x,y);
			}
			else if(opt==4){//kth
				write(kth(rt[i],v));
			}
			else if(opt==5){//pre
				split(rt[i],v-1,x,y);
				if(!t[x].siz) write(-inf);
				else write(kth(x,t[x].siz));
				rt[i]=merge(x,y);
			}
			else{//nxt
				split(rt[i],v,x,y);
				if(!t[y].siz) write(inf);
				else write(kth(y,1));
				rt[i]=merge(x,y);
			}
			putchar('\n');
		}
	}
	return 0;
}

可持久化文艺平衡树

一般来说,线段树是用来维护区间的,平衡树是用来维护权值的。那么既然有权值线段树,就会有区间平衡树。

区间平衡树能实现线段树实现不了的东西,比如翻转区间。

和可持久化普通平衡树一样,每次分裂合并新建节点,再加一个表示翻转的tag就行。每次先pushdown再操作。下传时给左右儿子打上标记并交换。

可持久化文艺平衡树
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5e5+5;
int n,rt[maxn],num;
ll lst;
struct node{
	int lc,rc,siz,prio,v,tg;
	long long sum;
}t[maxn<<6];
int newnode(int v){
	t[++num].siz=1;
	t[num].v=t[num].sum=v;//平衡树挂分指南:初始化不完全! 
	t[num].prio=rand();
	return num;
} 
int cpy(int x){
	int y=newnode(0);
	t[y]=t[x];
	return y;
}
void pushup(int x){
	t[x].siz=t[t[x].lc].siz+t[t[x].rc].siz+1;
	t[x].sum=t[t[x].lc].sum+t[t[x].rc].sum+t[x].v;
}
void pushdown(int x){
	if(!t[x].tg) return ;
	if(t[x].lc) t[x].lc=cpy(t[x].lc);
	if(t[x].rc) t[x].rc=cpy(t[x].rc);
	swap(t[x].lc,t[x].rc);
	if(t[x].lc) t[t[x].lc].tg^=1;
	if(t[x].rc) t[t[x].rc].tg^=1;
	t[x].tg=0;
}
void split(int p,int v,int &x,int &y){
	if(!p){
		x=y=0; return ;
	}
	pushdown(p);
	if(v>t[t[p].lc].siz){//这里想清楚,包含了v==t[t[p].lc].siz+1的情况,此时我们希望这个节点归于x子树 
		x=cpy(p);
		split(t[x].rc,v-t[t[p].lc].siz-1,t[x].rc,y);
		pushup(x);
	}
	else{
		y=cpy(p);
		split(t[y].lc,v,x,t[y].lc);
		pushup(y);
	}
}
int merge(int x,int y){
	if(!x||!y) return x+y;
	pushdown(x),pushdown(y);
	if(t[x].prio<t[y].prio){
		int p=newnode(0); t[p]=t[x];
		t[p].rc=merge(t[p].rc,y);
		pushup(p); return p;
	}
	else{
		int p=newnode(0); t[p]=t[y];
		t[p].lc=merge(x,t[p].lc);
		pushup(p); return p;
	}
}
ll read(){
	char ch=getchar(); ll f=1,x=0;
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}
void write(int x){
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x/10);
	putchar('0'+x%10);
}
int main(){
	srand(time(0));
	n=read();
	for(int i=1;i<=n;i++){
		int p=read(),opt=read(),x,y,z,l=read()^lst,r;
		rt[i]=rt[p];
		if(opt==1){
			r=read()^lst;
			split(rt[i],l,x,y);
			rt[i]=merge(x,merge(newnode(r),y));
		}
		else if(opt==2){
			split(rt[i],l-1,x,y);
			split(y,1,z,y);
			rt[i]=merge(x,y);
		}
		else if(opt==3){
			r=read()^lst;
			split(rt[i],l-1,x,y);
			split(y,r-l+1,z,y);
			t[z].tg^=1;
			rt[i]=merge(x,merge(z,y));
		}
		else{
			r=read()^lst;
			split(rt[i],l-1,x,y);
			split(y,r-l+1,z,y);
			lst=t[z].sum;
			rt[i]=merge(x,merge(z,y));
			printf("%lld\n",lst);
		}
	}
	return 0;
}

可持久化trie

说白了跟主席树(可持久化线段树)一样,每次先复制上一个节点,再插入当前即可(相当于做前缀和)。这里写成取地址的递归建树会显得更像主席树,个人觉得更好看(

查询也类似主席树。就是在01trie中查询某数和某段区间中数的异或最大值,先看能不能取到相反的字符,如果(相减后得到的)这段区间中有相反字符,加入贡献并走这边;否则走另一边。

有时写着写着代码可能会觉得有点奇怪、和主席树不那么一样,是因为字典树的信息存在边上,每条转移边代表了一个字符。稍加注意即可。

[HEOI2013] ALO
//这题二分+线段树 log^2就能过 
#include<bits/stdc++.h>
#define lid (id<<1)
#define rid (id<<1|1)
using namespace std;
const int maxn=5e4+5;
int n,rt[maxn],a[maxn],cnt,tr[maxn<<2],ans;
struct node{
	int c[2],siz;
}t[maxn*40];
void insert(int &p,int lst,int dep,int v){
	p=++cnt,t[p]=t[lst],t[p].siz++;
	if(dep<0) return ;
	int x=(v>>dep)&1;
	insert(t[p].c[x],t[lst].c[x],dep-1,v);
}
int query(int p,int lst,int dep,int v){
	if(dep<0) return 0;
	int x=(v>>dep)&1;
	if(t[t[p].c[!x]].siz>t[t[lst].c[!x]].siz)
		return query(t[p].c[!x],t[lst].c[!x],dep-1,v)+(1<<dep);
	else return query(t[p].c[x],t[lst].c[x],dep-1,v);
}
void build(int id,int l,int r){
	if(l==r) return tr[id]=a[l],void();
	int mid=(l+r)>>1;
	build(lid,l,mid),build(rid,mid+1,r);
	tr[id]=max(tr[lid],tr[rid]);
} 
int qmax(int id,int l,int r,int vl,int vr){
	if(vl<=l&&r<=vr) return tr[id];
	int mid=(l+r)>>1,res=0;
	if(vl<=mid) res=qmax(lid,l,mid,vl,vr);
	if(vr>mid) res=max(res,qmax(rid,mid+1,r,vl,vr));
	return res;
}
int check1(int l,int r,int now){
	int mid,ret=0,lim=r;
	if(r<1||r<l) return ret;
	while(l<=r){
		mid=(l+r)>>1;
		if(qmax(1,1,n,mid,lim)>=now){
			ret=mid;
			l=mid+1;
		} else r=mid-1;
	}
	return ret;
}
int check2(int l,int r,int now){
	int mid,ret=n+1,lim=l;
	if(l>n||r<l) return ret;
	while(l<=r){
		mid=(l+r)>>1;
		if(qmax(1,1,n,lim,mid)>=now){
			ret=mid;
			r=mid-1;
		} else l=mid+1;
	}
	return ret;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		insert(rt[i],rt[i-1],31,a[i]);
	}
	build(1,1,n);
	for(int i=1;i<=n;i++){
		int l1=check1(1,i-1,a[i]),r1=check2(i+1,n,a[i]),l2=check1(1,l1-1,a[i]),r2=check2(r1+1,n,a[i]);
//		printf("%d %d %d %d %d\n",l2,l1,i,r1,r2);
		if(r1<=n) ans=max(ans,query(rt[r2-1],rt[l1],31,a[i]));
		if(l1) ans=max(ans,query(rt[r1-1],rt[l2],31,a[i]));
	}
	printf("%d",ans);
	return 0;
} 
非递归写法
int insert(int lst,string st){
	int ret=++cnt,p=cnt;
	for(int i=0;i<st.size();i++){
		int x=st[i]-'a';
		tr[p]=tr[lst],tr[p].siz++;
		tr[p].c[x]=++cnt;//这里必须新建节点 
		p=tr[p].c[x],lst=tr[lst].c[x];
	}
	tr[p].siz=tr[lst].siz+1;//注意细节,是要减的 
	return ret;
}
int query(int x,int y,int g){
	for(int i=0;i<t.size();i++){
		int p=t[i]-'a';
		x=tr[x].c[p],y=tr[y].c[p],g=tr[g].c[p];
	}
	return tr[x].siz+tr[y].siz-tr[g].siz*2;
}

可持久化01trie挂分指南:(别问我怎么知道的

1)空间开小。因为每次在insert时,除了每个数位插入,root还要占用空间。

2)边界处理不清。如果用01trie维护异或和的问题,需要把异或和转化为前缀和去做,然而01trie本身就是一个前缀和,因此边界上有时会产生形如l-2的东西(一定要想清楚该不该-1!!!)。原因就是,可能直接从最开始选(l=1时),那么此时前缀和就应该-pre[0],而正常来说,trie从1开始维护。所以需要提前加入rt[0]这个点,访问到时进行特判。一车细节。

可持久化并查集

考虑可持久化线段树维护的东西,实际上就是可持久化数组

那么并查集本质上就是维护了fa数组和rk/siz数组。注意这里不能用路径压缩的原因是,路径压缩的时间复杂度是均摊的,如果构造数据,使它每次返回到那个需要修改一堆节点的操作,时空复杂度就都假了。

直接用可持久化线段树维护fa和rk数组就行,实现和普通并查集一样。想清楚,访问数组中的元素,需要在线段树中拿对应下标去查。

可持久化并查集
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
int n,m,cnt,rt[maxn];
struct node{
	int lc,rc,fa,dep;
}t[maxn<<5];
void build(int &x,int l,int r){
	x=++cnt;
	if(l==r) return t[x].fa=l,void();
	int mid=(l+r)>>1;
	build(t[x].lc,l,mid);
	build(t[x].rc,mid+1,r);
}
void add(int &x,int l,int r,int pos){
	t[++cnt]=t[x],x=cnt;
	if(l==r) return t[x].dep++,void();
	int mid=(l+r)>>1;
	if(pos<=mid) add(t[x].lc,l,mid,pos);
	else add(t[x].rc,mid+1,r,pos);
}
void change(int &x,int l,int r,int pos,int fa){
	t[++cnt]=t[x],x=cnt;
	if(l==r) return t[x].fa=fa,void();
	int mid=(l+r)>>1;
	if(pos<=mid) change(t[x].lc,l,mid,pos,fa);
	else change(t[x].rc,mid+1,r,pos,fa);
}
int query(int x,int l,int r,int pos){
	if(l==r) return x;
	int mid=(l+r)>>1;
	if(pos<=mid) return query(t[x].lc,l,mid,pos);
	else return query(t[x].rc,mid+1,r,pos);
}
int find(int root,int x){
	int g=query(root,1,n,x);//数组中第x个数的fa为x,即根 
	if(t[g].fa==x) return g;
	return find(root,t[g].fa);
}
void merge(int i,int x,int y){
	x=find(rt[i],x),y=find(rt[i],y);
	if(x==y) return ;
	if(t[x].dep>t[y].dep) swap(x,y);
	change(rt[i],1,n,t[x].fa,t[y].fa);//f[find(x)]=find(y)
	if(t[x].dep==t[y].dep) add(rt[i],1,n,t[y].fa);//按秩合并 
}
void check(int i,int x,int y){
	x=find(rt[i],x),y=find(rt[i],y);
	if(t[x].fa==t[y].fa) printf("1\n");
	else printf("0\n");
}
int main(){
	scanf("%d%d",&n,&m);
	build(rt[0],1,n);
	for(int i=1;i<=m;i++){
		int opt,x,y;
		scanf("%d%d",&opt,&x);
		rt[i]=rt[i-1];
		if(opt==1){
			scanf("%d",&y);
			merge(i,x,y);
		}
		else if(opt==2) rt[i]=rt[x];
		else{
			scanf("%d",&y);
			check(i,x,y);
		} 
	}
	return 0;
} 

本文作者:YYYmoon

本文链接:https://www.cnblogs.com/YYYmoon/p/18651957

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   YYYmoon  阅读(23)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起