http://www.lydsy.com/JudgeOnline/problem.php?id=3674
3674: 可持久化并查集加强版
Time Limit: 15 Sec Memory Limit: 256 MBSubmit: 3225 Solved: 1192
[Submit][Status][Discuss]
Description
Description:
自从zkysb出了可持久化并查集后……
hzwer:乱写能AC,暴力踩标程
KuribohG:我不路径压缩就过了!
ndsf:暴力就可以轻松虐!
zky:……
n个集合 m个操作
操作:
1 a b 合并a,b所在集合
2 k 回到第k次操作之后的状态(查询算作操作)
3 a b 询问a,b是否属于同一集合,是则输出1否则输出0
请注意本题采用强制在线,所给的a,b,k均经过加密,加密方法为x = x xor lastans,lastans的初始值为0
0<n,m<=2*10^5
Input
Output
Sample Input
5 6
1 1 2
3 1 2
2 1
3 0 3
2 1
3 1 2
1 1 2
3 1 2
2 1
3 0 3
2 1
3 1 2
Sample Output
1
0
1
0
1
题意很简单,就是要你写个可持久化并查集,建议做这题之前先去看我的上一篇博客——可持久化线段树,以及你必须会普通版的并查集。
如何构造一个可持久化并查集呢?我们知道并查集是由数组实现的,所以只要能写出可持久化数组,自然就能写出可持久化并查集了。查了一些资料,可持久化数组似乎就是可持久化线段树(这个我也不太确定),但是我们还是可以用线段树代替一下数组实现并查集,我们只需要线段树的叶节点去合并就可以了,实际上线段树上面的区间都是没什么用的,但是又不能删掉,因为我们还要维护root[i]。同时要注意并查集合并的时候不要路径压缩,因为压缩后会影响太多点,返回历史版本会很难,所以我们选择人畜无害的按秩合并,同样可以快到飞起。具体实现看代码吧。可持久化并查集可以用pb_ds库的rope,现成模板,我也贴一份模板在下面了,不过这个跑起来的时间是自己写的版本的两倍。(PS:其实是可以进行路径压缩的,但是空间要稍微开大一点)
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #include <vector> #include <queue> #include <string> #include <stack> #include <map> #include <set> #include <bitset> #define X first #define Y second #define clr(u,v); memset(u,v,sizeof(u)); #define in() freopen("data","r",stdin); #define out() freopen("ans","w",stdout); #define Clear(Q); while (!Q.empty()) Q.pop(); #define pb push_back using namespace std; typedef long long ll; typedef pair<int, int> pii; const int maxn = 2e5 + 10; const int INF = 0x3f3f3f3f; struct Tree { int l, r, fa; } T[maxn<<5]; int root[maxn], cnt = 0; int deep[maxn];//并查集深度 int n, m; void build(int l, int r, int &x)//先建树,进行初始化,如果有多case记得cnt初始化为0 { x = ++cnt; if (l == r) { T[x].fa = l; deep[l] = 1; return ; } int mid = (l + r) >> 1; build(l, mid, T[x].l); build(mid + 1, r, T[x].r); } int query(int l, int r, int rt, int pos)//查询pos在线段树中的位置 { if (l == r) return rt; int mid = (l + r) >> 1; if (mid >= pos) return query(l, mid, T[rt].l, pos); else return query(mid + 1, r, T[rt].r, pos); } int find(int x, int cur)//并查集的找祖先 { int pos = query(1, n, root[cur], x); if (T[pos].fa != x) return find(T[pos].fa, cur); return T[pos].fa; } void update(int l, int r, int &x, int y, int pos, int fa) { T[++cnt] = T[y];//继承上一个版本 x = cnt;//与父节点连接 if (l == r) { T[x].fa = fa;//更新fa值 return ; } int mid = (l + r) >> 1; if (mid >= pos) update(l, mid, T[x].l, T[y].l, pos, fa); else update(mid + 1, r, T[x].r, T[y].r, pos, fa); } void mix(int x, int y, int cur) { int fx = find(x, cur), fy = find(y, cur); if (deep[fx] > deep[fy]) swap(fx, fy);//按秩合并 if (fx == fy) return ; deep[fy] += deep[fx]; update(1, n, root[cur], root[cur-1], fx, fy);//在线段树fx的位置上更新为fy } int main() { #ifdef LOCAL in(); #endif scanf("%d%d", &n, &m); build(1, n, root[0]); int cur = 0, lastans = 0; while (m--) { int op, a, b, k; scanf("%d", &op); if (op == 1) { scanf("%d%d", &a, &b); a ^= lastans; b ^= lastans; root[cur+1] = root[cur]; mix(a, b, ++cur); } else if (op == 2) { scanf("%d", &k); k ^= lastans; root[++cur] = root[k];//这里不建议改成cur = k ,因为他可能跳到前面后又跳回去,这样会覆盖掉历史版本 } else { scanf("%d%d", &a, &b); a ^= lastans; b ^= lastans; root[cur+1] = root[cur]; ++cur; lastans = (find(a, cur) == find(b, cur)); printf("%d ", lastans); } } return 0; }
pb_ds库的rope:
#include <bits/stdc++.h> #include <cstdio> #include <ext/rope> using namespace std; using namespace __gnu_cxx; const int maxn = 2e5 + 10; rope <int> *f[maxn]; rope <int> :: iterator it; int id[maxn]; int n, m, op, a, b; int find (int &t, int x) { if (f[t]->at(x) == x) return x; int fa = find(t, f[t]->at(x)); if (fa == f[t]->at(x)) return fa; f[t]->replace(x, fa); return fa; } void mix(int &t, int x, int y) { int fx = find(t, x), fy = find(t, y); f[t]->replace(fy, fx); } int lastans = 0; int main() { scanf("%d%d", &n, &m); for (int i = 0; i <= n; ++i) id[i] = i; f[0] = new rope<int>(id, id + n + 1); for (int i = 1; i <= m; ++i) { f[i] = new rope <int>(*f[i-1]); scanf("%d", &op); if (op == 1) { scanf("%d%d", &a, &b); mix(i, a ^ lastans, b ^ lastans); } else if (op == 2) { scanf("%d", &a); f[i] = f[lastans^a]; } else if (op == 3) { scanf("%d%d", &a, &b); printf("%d ", lastans = find(i, a ^ lastans) == find(i, b ^ lastans)); } } return 0; }
附上路径压缩版本,路径压缩要updata的次数更多,所以空间也会大点,实测和按秩合并速度差不多,大概是updata也花了时间
int find(int x, int cur)//并查集的找祖先 { int pos = query(1, n, root[cur], x); if (T[pos].fa != x) { int temp = find(T[pos].fa, cur); update(1, n, root[cur], root[cur], T[pos].fa, temp); return temp; } return T[pos].fa; }