P3402 可持久化并查集 模板题

给定初始n个集合,开始每个集合中只有i一个数,
之后给定m次操作
1 a b代表合并ab所在的集合
2 k 代表回到第二次操作
3 a b 代表查询a b是否数属于同一集合 是输出1,否输出0

很明显操作2说明了我们需要维护的是一个可持久化并查集,因为他需要支持历史版本的回溯。而既然是并查集,我们知道普通并查集的合并查询操作的核心都是fa数组,也就是父节点,因此可持久化并查集一个需要维护的就是我们的fa数组可持久化

而并查集中有两种我们常见我的优化方式一是路径压缩,二是按秩合并(就是按数高小的合并到大的上),整体上减小树的高度,从而达到优化效果。但是第一种优化路径压缩在可持久化结构中,会产生巨大的空间消耗(好像是),因此我们只能采用第二种路径压缩的方式来优化。这样我们就需要再来维护另一个可持久化的数组dep数组,代表并查集的高

剩下的具体实现看代码吧
借鉴大佬的代码 还有讲解视频挺清晰的

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int MAXN = 2e5+7;
//可持久化 并查集 经典模板题
//1 a b 合并ab所在的集合 并查集的操作
//2 k 回到第k次操作由此可以想到 用一个 可持久化数组 维护一下节点
//3 a b 询问ab是否在同一个区间 并查集的询问操作
//用主席数维护一个fa和一个dep数组
//merge有两种写法 一种是 merge里面带判断 另一种先判断秩的关系 在merge

int dep[MAXN],fa[MAXN],n,size,m;//dep和fa这两个数组时用来可持久化维护的 相当于记录不同功能的不同版本
struct HJT_tree
{
    
    
	int l,r,sum;//这个sum 一部分是记录fa数组的祖先节点的值 另一部分是记录dep数组的值 用到哪个时候调用相应的内存池即可
}tree[MAXN*40*2];
//可持久化数组操作
void build(int &now,int l,int r)//需要对now进行修改的时候 再传引用即(&now)
{
    
    
	now = ++size;
	if(l == r){
    
    
		tree[now].sum = l;//dep初始化都是0
		return ;
	}
	int mid = (l+r)>>1;
	build(tree[now].l,l,mid);
	build(tree[now].r,mid+1,r);
}

void update(int l,int r,int pre,int &now,int pos,int val)
{
    
    
	now = ++size;
	tree[now] = tree[pre];
	if(l == r){
    
    
		tree[now].sum = val;
		return ;
	}
	int mid = (l+r)>>1;
	if(pos <= mid) update(l,mid,tree[pre].l,tree[now].l,pos,val);
	else update(mid+1,r,tree[pre].r,tree[now].r,pos,val);
}

int query(int now,int l,int r,int pos)
{
    
    
	if(l == r) return tree[now].sum;
	int mid = (l+r)>>1;
	if(pos <= mid) return query(tree[now].l,l,mid,pos);
	else return query(tree[now].r,mid+1,r,pos);
}
//以上是树的操作

//并查集的相关操作
int Find(int now,int x)
{
    
    
	int fx = query(fa[now],1,n,x);
	return fx == x ? x : Find(now,fx);//注意这里不要路径压缩
}

void merge(int now,int x,int y)
{
    
    
	int fx = Find(now-1,x),fy = Find(now-1,y);//这里是前一个版本的操作 因为新的版本还没生成 (也可以在主函数里传前一个版的参数)
	if(fx == fy){
    
    
		fa[now] = fa[now-1];
		dep[now] = dep[now-1];
		return ;
	}
	else{
    
    //注意后面的操作都是对fx和fy操作 合并修改的时候都是既要改深度也要改高度
		int depx = query(dep[now-1],1,n,fx);
		int depy = query(dep[now-1],1,n,fy);
		if(depx != depy){
    
    //这里 高度不一样的话 把小的连接到大的上面来完成
			if(depx > depy) swap(fx,fy);
			update(1,n,fa[now-1],fa[now],fx,fy);
			dep[now] = dep[now-1];
		}
		if(depx == depy){
    
    
			update(1,n,fa[now-1],fa[now],fx,fy);
			update(1,n,dep[now-1],dep[now],fy,depy+1);
		}
	}
}
//以上是并查集的相关操作

int main()
{
    
    
	scanf("%d%d",&n,&m);
	build(fa[0],1,n);//这类dep初始化都看成0即可 所以不需要 我们构建
	for(int v = 1;v <= m;v ++){
    
    
		int op,a,b,k;
		scanf("%d",&op);
		switch(op){
    
    
			case 1:
				scanf("%d%d",&a,&b);
				merge(v,a,b);
				break;
			case 2:
				scanf("%d",&k);
				fa[v] = fa[k];//回溯状态
				dep[v] = dep[k];
				break; 
			case 3:
				scanf("%d%d",&a,&b);
				fa[v] = fa[v-1];//产生新状态
				dep[v] = dep[v-1];
				int fa = Find(v,a),fb = Find(v,b);
				printf(fa == fb?"1\n":"0\n");
				break;
		}
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_45672411/article/details/108486779
今日推荐