题意:n个集合 m个操作
操作:
1 a b 合并a,b所在集合
2 k 回到第k次操作之后的状态(查询算作操作)
3 a b 询问a,b是否属于同一集合,是则输出1否则输出0
操作:
1 a b 合并a,b所在集合
2 k 回到第k次操作之后的状态(查询算作操作)
3 a b 询问a,b是否属于同一集合,是则输出1否则输出0
0<n,m<=2*10^4
思路:裸的可持久化并查集。
我们知道普通的并查集就是一个pre数组,现在需要可持久化,维护历史版本的pre数组。和主席树是有相通之处的,
都是每个节点维护了一棵线段树,然后每次修改只会修改到一条链,这道题也是一样的。
可持久化线段树维护可持久化数组从而维护可持久化并查集.
可持久化线段树里维护的是并查集中的信息。一个pre,一个启发式合并的size(因为不能压缩路径,所以要降低查询
根的复杂度,让子树深度尽量平衡,要注意下如果两个深度相同的合并,深度需要+1, 不同的话合并后的深度就是
深度大的那个)。
并查集的Find操作就从可持久化线段树里找。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e4*20;
int T[maxn], pre[maxn], deep[maxn], lson[maxn], rson[maxn];
int cnt, n, m;
int build(int l, int r)
{
int rt = ++cnt;
if(l == r) pre[rt] = l;
else
{
int mid = (l+r)/2;
lson[rt] = build(l, mid);
rson[rt] = build(mid+1, r);
}
return rt;
}
int query(int root, int l, int r, int pos)
{
if(l == r) return root;
int mid = (l+r)/2;
if(pos <= mid) return query(lson[root], l, mid, pos);
else return query(rson[root], mid+1, r, pos);
}
int Find(int root, int x)
{
int p = query(root, 1, n, x);
if(x == pre[p]) return p;
else return Find(root, pre[p]);
}
void update(int root, int l, int r, int pos)
{
if(l == r)
{
deep[root]++;
return ;
}
int mid = (l+r)/2;
if(pos <= mid) update(lson[root], l, mid, pos);
else update(rson[root], mid+1, r, pos);
}
int modify(int Pre, int l, int r, int pos, int val)
{
int rt = ++cnt;
if(l == r)
{
pre[rt] = val;
deep[rt] = deep[Pre];
return rt;
}
lson[rt] = lson[Pre], rson[rt] = rson[Pre];
int mid = (l+r)/2;
if(pos <= mid) lson[rt] = modify(lson[Pre], l, mid, pos, val);
else rson[rt] = modify(rson[Pre], mid+1, r, pos, val);
return rt;
}
void union_set(int fa, int fb, int i)
{
if(deep[fa] > deep[fb]) swap(fa, fb);
T[i] = modify(T[i-1], 1, n, pre[fa], pre[fb]);
if(deep[fa] == deep[fb]) update(T[i], 1, n, pre[fb]);
}
int main(void)
{
while(cin >> n >> m)
{
cnt = 0;
T[0] = build(1, n);
for(int i = 1; i <= m; i++)
{
int cmd, x, y;
scanf("%d%d", &cmd, &x);
if(cmd == 1)
{
scanf("%d", &y);
T[i] = T[i-1];
int fa = Find(T[i], x), fb = Find(T[i], y);
if(pre[fa] != pre[fb]) union_set(fa, fb, i);
}
else if(cmd == 2)
{
T[i] = T[x];
}
else
{
scanf("%d", &y);
T[i] = T[i-1];
int fa = Find(T[i], x), fb = Find(T[i], y);
puts(pre[fa]==pre[fb] ? "1" : "0");
}
}
}
return 0;
}
本文介绍了一种解决集合合并及回滚问题的数据结构——可持久化并查集。通过维护多个版本的并查集,支持操作回溯,实现集合状态的恢复。文章详细解析了算法思路,并提供了完整的代码实现。

142

被折叠的 条评论
为什么被折叠?



