版权声明: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
题解
-
这题不知道出题人什么做法,但是代码很短的样子
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;
}