给定
n
n
n 个集合,第
i
i
i 个集合内初始状态下只有一个数,为
i
i
i。
有
m
m
m 次操作。操作分为
3
3
3 种:
1 a b 合并 a , b a,b a,b 所在集合
2 k 回到第 k k k 次操作(执行三种操作中的任意一种都记为一次操作)之后的状态
3 a b 询问 a , b a,b a,b 是否属于同一集合,如果是则输出 1 1 1 ,否则输出 0 0 0
可持久化并查集模板
介绍
并查集通常来说是不可逆的,因为我们存在很多骚操作,让并查集的复杂度降到每次查询均摊
O
(
1
)
O(1)
O(1),比如路径压缩
每次修改某些点的
f
a
t
h
e
r
father
father信息,达到较快的速度查询
当然也不是一定不可逆,可撤销并查集就能让操作可逆,并查集能够后悔操作
可撤销并查集
这里大概说一下,可撤销并查集就是用结构体存储每次并查集路径压缩的前后的
f
a
t
h
e
r
father
father值是什么,也就是只要某个点的
f
a
t
h
e
r
father
father信息在压缩过程中改变了,就用结构体存起来,用栈维护修改序列
对于某次撤回操作,我们直接撤回到某个版本,当然我们也可以快进过去(用数组模拟栈的话比较好实现)
但是局限性是什么呢?
如果从版本
A
A
A,退回到某个版本
B
B
B后,(退回的代价是
O
(
n
)
O(n)
O(n)),在
B
B
B这个版本上进行修改成为了版本
C
C
C,
A
A
A和
C
C
C都是属于
B
B
B的一个分支,在
A
A
A和
C
C
C之间随意切换的代价是,
O
(
n
)
O(n)
O(n),(用数组存结构体记录操作情况,操作加上版本信息,理论上能够实现)
这里劣势已经很明显了,可以初步得出可撤销并查集适用于短期内,撤销某次错误操作的情景
可持久化并查集
如果要在高频率访问操作时间轴上的任意版本的情况下,如何维护并查集呢?
发现,并查集的核心就是
f
a
t
h
e
r
father
father数组,这里存储了每个点属于哪个集合
我们如果能够将
f
a
t
h
e
r
father
father数组持久化下来就不能够实现,在多个版本随意切换了吗?
这里借用主席树的思想。
对于静态区间第
K
K
K大,我们记录的是前缀和,通过不同的版本进行差分得到某段区间,某个权值的个数。(稍微提一下,因为是前缀和,所以静态的不支持修改,修改需要在线树套树or离线整体二分)
但是我们这里不需要前缀和,我们只需要某个版本的信息就行了!
这可太好了,使用简单的主席树就能够维护啊。
对于某个版本,我每次只修改
l
o
g
n
logn
logn个节点的信息,空间也满足了
具体是
初始建立一颗原始的主席树,每个节点的
f
a
t
h
e
r
father
father都是自己
最后,根据主席树的操作,将修改链上的节点先克隆(clone),在克隆版本上进行修改,可知每次只需要修改
l
o
g
n
logn
logn个节点,每次修改就是一个版本
修改
f
a
t
h
e
r
father
father的操作通常在某些骚操作路径压缩或按秩合并的时候发生
这里就出现了抉择,使用路径压缩还是按秩合并呢?(这里的按秩合并是按高度)
理论,两者皆可,反正我们能够维护每个版本的
f
a
t
h
e
r
father
father信息
但是我们分析一下
对于路径压缩,在father[x] = find(father[x])
的时候,会修改一次
对于某些特意构造的数据,就会造成某个版本需要修改很多次
f
a
t
h
e
r
father
father,那么所有版本都是这么多修改的话,即使是动态开点线段树,也支撑不了如此巨大的内存消耗,所以在特殊情况下,极容易
M
L
E
MLE
MLE
对于按秩合并,由于没有为了刻意追求速度,将时间让步于空间,得到每次修改并查集只需要在主席树上修改一次的优秀性质
小细节注意:
-
b
u
i
l
d
build
build 里面检测
r
t
rt
rt为0的时候调用了
c
l
o
n
e
clone
clone,其实每次都是克隆0号元素,等于说新建节点了,这里可以直接改为
rt=++indx;
- 代码里面 f i n d find find调用的 q u e r y query query并不是返回父亲的值就行了,因为我们还需要当前父亲的子树高度是多少(方便按秩合并),所以得返回存这个信息的节点编号。(如果仅仅只要看是否在同一集合中,返回父亲的值是可以的,具体情况不同)
- 按秩合并,只有两个集合的高度/深度相同时,才需要对另一个进行高度修改。因为此时必然有一个挂在另一个的下面,高度应该加 1 1 1
代码
//P3402
/*
@Author: YooQ
*/
#include <bits/stdc++.h>
using namespace std;
#define sc scanf
#define pr printf
#define ll long long
#define FILE_OUT freopen("out", "w", stdout);
#define FILE_IN freopen("in", "r", stdin);
#define debug(x) cout << #x << ": " << x << "\n";
#define AC 0
#define WA 1
#define INF 0x3f3f3f3f
const ll MAX_N = 1e6+5;
const ll MOD = 1e9+7;
int N, M, K;
int arr[MAX_N];
struct Tr {
int fa, d, l, r;
}tr[MAX_N<<4];
int root[MAX_N];
int rcnt = 0;
int indx = 0;
int clone(int rt) {
tr[++indx] = tr[rt];
return indx;
}
void build(int& rt, int l, int r) {
if (!rt) rt = clone(rt);
if (l == r) {
tr[rt].fa = l;
tr[rt].d = 1;
return;
}
int mid = l + ((r-l)>>1);
build(tr[rt].l, l, mid);
build(tr[rt].r, mid+1, r);
}
void update(int& rt, int l, int r, int x, int d) {
rt = clone(rt);
if (l == r) {
tr[rt].d += d;
return;
}
int mid = l + ((r-l)>>1);
if (x <= mid) update(tr[rt].l, l, mid, x, d);
if (x > mid) update(tr[rt].r, mid+1, r, x, d);
}
int query(int rt, int l, int r, int x) {
if (l == r) {
return rt;
}
int mid = l + ((r-l)>>1);
if (x <= mid) return query(tr[rt].l, l, mid, x);
if (x > mid) return query(tr[rt].r, mid+1, r, x);
}
int find(int rt, int x) {
int y = query(rt, 1, N, x);
return tr[y].fa == x ? y : find(rt, tr[y].fa);
}
void merge(int &rt, int l, int r, int x, int y) {
rt = clone(rt);
if (l == r) {
tr[rt].fa = y;
return;
}
int mid = l + ((r-l)>>1);
if (x <= mid) merge(tr[rt].l, l, mid, x, y);
if (x > mid) merge(tr[rt].r, mid+1, r, x, y);
}
void solve(){
sc("%d%d", &N, &M);
build(root[0], 1, N);
int opt, x, y;
for (int i = 1; i <= M; ++i) {
sc("%d", &opt);
root[i] = root[i-1];
if (opt == 1) {
sc("%d%d", &x, &y);
x = find(root[i], x);
y = find(root[i], y);
if (tr[x].fa == tr[y].fa) continue;
if (tr[x].d > tr[y].d) swap(x, y);
merge(root[i], 1, N, tr[x].fa, tr[y].fa);
if (tr[x].d == tr[y].d) {
update(root[i], 1, N, tr[y].fa, 1);
}
} else if (opt == 2) {
sc("%d", &x);
root[i] = root[x];
} else {
sc("%d%d", &x, &y);
x = find(root[i], x);
y = find(root[i], y);
if (tr[x].fa == tr[y].fa) {
puts("1");
} else {
puts("0");
}
}
}
}
signed main()
{
#ifndef ONLINE_JUDGE
//FILE_IN
FILE_OUT
#endif
int T = 1;//cin >> T;
while (T--) solve();
return AC;
}