BZOJ 3673 可持久化并查集 by zky 可持续化线段树+并查集启发式合并

版权声明:https://blog.csdn.net/huashuimu2003 https://blog.csdn.net/huashuimu2003/article/details/86751273

题目

BZOJ 3673
LUOGU 3402
Description

n个集合 m个操作
操作:
1 a b 合并a,b所在集合
2 k 回到第k次操作之后的状态(查询算作操作)
3 a b 询问a,b是否属于同一集合,是则输出1否则输出0
0<n,m<=2*10^4

Input

Output

Sample Input

5 6
1 1 2
3 1 2
2 0
3 1 2
2 1
3 1 2

Sample Output

1
0
1

HINT

Source

出题人大SB

扫描二维码关注公众号,回复: 5771460 查看本文章

题解

  • 这题不知道出题人什么做法,但是代码很短的样子
    UPD:出题人用的是rope,即stl中的可持久化平衡树
    KuribohG神犇告诉了我可以用可持久化线段树实现可持久化数组T T
    既然都有可持久化数组了,只要用个再并查集的启发式合并就能妥妥的水过了(这样每次只要修改一个fa)。

  • 并查集的启发式合并就是按秩合并,初始所有集合秩为0
    合并把秩小的树根的父亲设为秩大的树根
    如果秩相同,则随便选取一个作为父节点并将它的秩+1
    秩和fa一样维护。

  • 但是其实这题数据随机的话随便合并就行了,根本不用按秩合并什么的
    UPD:秩其实有的时候很不好用,维护子树大小比较赞。。。
    另外,ndsf发现只要直接暴力就能虐了T T。

引用自hzwer

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1,ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
struct rec
{
	int l,r;
}tree[maxn*30];
int Ed[maxn],tot;//Ed[]是版本号,tot是节点总数(这些就是主席树啦)
int n,m,fa[maxn*30],deep[maxn*30];//deep[]存最大深度,fa[]存一个点在某个版本的父亲
inline void build(int &now,int l,int r)
{
	now=++tot;
	if (l==r)
	{
		fa[now]=l;//初始版本:父亲是自己,就像并查集初始化每个点的父亲是它自己
		return ;
	}
	int mid=(l+r)>>1;
	build(tree[now].l,l,mid);
	build(tree[now].r,mid+1,r);
}
//主席树维护的是:每一个版本,每一个点的父亲是谁
inline void update(int &now,int last,int l,int r,int x,int f)//把x的父亲改成f
{
	now=++tot;
	if (l==r)
	{
		fa[now]=f;
		deep[now]=deep[last];
		return ;
	}
	tree[now]=tree[last];//deep[]用于启发式合并
	int mid=(l+r)>>1;
	if (x<=mid)
		update(tree[now].l,tree[last].l,l,mid,x,f);
	else
		update(tree[now].r,tree[last].r,mid+1,r,x,f);
}

inline int query(int now,int l,int r,int x)//询问某一个版本的一个点的父亲
{
	if (l==r)
		return now;
	int mid=(l+r)>>1;
	if (x<=mid)
		return query(tree[now].l,l,mid,x);
	else
		return query(tree[now].r,mid+1,r,x);
}

inline void add(int now,int l,int r,int x)//把某一个并查集联通块中每一个点的深度加一
{
	if (l==r)
	{
		++deep[now];
		return ;
	}
	int mid=(l+r)>>1;
	if (x<=mid)
		add(tree[now].l,l,mid,x);
	else
		add(tree[now].r,mid+1,r,x);
}
inline int get(int ed,int x)//ed 版本编号
{
	register int f=query(ed,1,n,x);//查询在这一版本里 一个点的父亲
	if (x==fa[f]) return f;
	return get(ed,fa[f]);//不带路径压缩的并查集
}
int main()
{
	read(n);read(m);
	build(Ed[0],1,n);
	for (register int opt,k,a,b,i=1;i<=m;++i)
	{
		read(opt);
		if (opt==1)
		{
			Ed[i]=Ed[i-1];
			read(a);read(b);
			register int f1=get(Ed[i],a);
			register int f2=get(Ed[i],b);
			if (fa[f1]==fa[f2]) continue;
			if (deep[f1] > deep[f2])
				swap(f1,f2);//把大的往小的并,保证f1儿子节点数一定是小于等于f2
			update(Ed[i],Ed[i-1],1,n,fa[f1],fa[f2]);
			if (deep[f1]==deep[f2])
				add(Ed[i],1,n,fa[f2]);//因为f2并到了f1,所以f1的深度要加1
          //我们用启发式合并来保证并查集合并的复杂度
		}
		else if (opt==2)
		{
			read(k);
			Ed[i]=Ed[k];
		}
		else
		{
			Ed[i]=Ed[i-1];
			read(a);read(b);
			register int f1=get(Ed[i],a);
			register int f2=get(Ed[i],b);
			if (fa[f1]==fa[f2]) puts("1");
			else puts("0");
		}
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/huashuimu2003/article/details/86751273
今日推荐