可持久化线段树维护启发式合并的可持久化并查集
BZOJ 3673: 可持久化并查集
n个集合 m个操作
操作:
1 a b 合并a,b所在集合
2 k 回到第k次操作之后的状态(查询算作操作)
3 a b 询问a,b是否属于同一集合,是则输出1否则输出0
0<n,m<=2*10^4
偶然看到这个数据结构,发现挺简单的,前提是会可持久化线段树,可持久化并查集可以说是可持久化线段树的应用。
并查集维护的是一个父节点指针数组,附带的有时又要维护秩之类的。实习可持久化,我们可以维护多个数组。这样空间肯定是会爆的。那么我们如果直接用主席树维护数组呢。改父亲结点,变成了单点更新。操作回滚变成了普通的主席树回滚。合并,如果不考虑压缩路径直接合并即可。由于主席树中压缩路径不方便回滚(个人感觉)。看了看大牛门的写法,大都是启发式合并,也就是(秩)。到此可持久化线段树可持久化数组可持久化并查集
看不懂的建议先学个前置技能。并查集的启发式合并,和主席树。
/**************************************************************
Problem: 3673
User: Q1143316492
Language: C++
Result: Accepted
Time:64 ms
Memory:64576 kb
****************************************************************/
/**
* BZOJ 3673 可持久化线段树 维护启发式合并的可持久化并查集
*/
//#pragma GCC optimize(2)
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
#include <map>
#include <set>
using namespace std;
typedef long long LL;
const int INF = 0x3F3F3F3F;
const int INT_MAX = 0x7FFFFFFF;
const LL LLINF = 0x3F3F3F3F3F3F3F3F;
const double PI = acos(-1);
const int MAXN = 2e5 + 10;
const int MAXM = 1e6 + 10;
struct Node {
int l, r, faz, rank;
} tree[MAXN * 20];
int n, m, root[MAXN], tot;
int build(int l, int r) {
int now = ++tot;
if(l == r) {
tree[now].faz = l;
tree[now].rank = 0;
return now;
}
int mid = (l + r) >> 1;
tree[now].l = build(l, mid);
tree[now].r = build(mid + 1, r);
return now;
}
int query(int rt, int l, int r, int x) {
if(l == r) {
return rt;
}
int mid = (l + r) >> 1;
if(x <= mid) {
return query(tree[rt].l, l, mid, x);
} else {
return query(tree[rt].r, mid + 1, r, x);
}
}
int find_root(int rt, int x) {
int y = query(rt, 1, n, x);
if(x == tree[y].faz) {
return y;
}
return find_root(rt, tree[y].faz);
}
int update(int rt, int l, int r, int pos, int val) {
int now = ++tot;
tree[now] = tree[rt];
if(l == r) {
tree[now].faz = val;
tree[now].rank = tree[rt].rank;
return now;
}
int mid = (l + r) >> 1;
if(pos <= mid) {
tree[now].l = update(tree[now].l, l, mid, pos, val);
} else {
tree[now].r = update(tree[now].r, mid + 1, r, pos, val);
}
return now;
}
void add(int now, int l, int r, int pos) {
if(l == r) {
tree[now].rank++;
return ;
}
int mid = (l + r) >> 1;
if(pos <= mid) {
add(tree[now].l, l, mid, pos);
} else {
add(tree[now].r, mid + 1, r, pos);
}
}
int main() {
while(~scanf("%d %d", &n, &m)) {
tot = 0;
root[0] = build(1, n);
for(int i = 1; i <= m; i++ ) {
int opt;
scanf("%d", &opt);
if(opt == 1) {
int a, b;
scanf("%d %d", &a, &b);
root[i] = root[i - 1];
int x = find_root(root[i], a);
int y = find_root(root[i], b);
if(tree[x].faz == tree[y].faz) {
continue;
}
if(tree[x].rank > tree[y].rank) {
swap(x, y);
}
root[i] = update(root[i - 1], 1, n, tree[x].faz, tree[y].faz);
if(tree[x].rank == tree[y].rank) {
add(root[i], 1, n, tree[y].faz);
}
} else if(opt == 2) {
int k;
scanf("%d", &k);
root[i] = root[k];
} else if(opt == 3) {
int a, b;
scanf("%d %d", &a, &b);
root[i] = root[i - 1];
a = find_root(root[i], a);
b = find_root(root[i], b);
if(tree[a].faz == tree[b].faz) {
puts("1");
} else {
puts("0");
}
}
}
}
return 0;
}