初涉可持久化并查集
离线做法 / 并查集与主席树的结合
什么是可持久化并查集
顾名思义,就是可持久化的并查集————即支持历史版本回溯的并查集。
比如NOI2018d1t1的两个log做法。
通常实现方法
离线做法
注意到按秩合并的并查集是支持撤销的(注意“撤销”和“删除”是两码事)。
于是与其他离线题目类似,在操作的拓扑图上顺序做下就行了(空间又小跑得还快)。
1 #include<bits/stdc++.h> 2 const int maxn = 100035; 3 const int maxm = 200035; 4 5 struct QRs 6 { 7 int opt,x,y; 8 }q[maxm]; 9 struct DSU 10 { 11 int top,stk[maxn],fa[maxn],tot[maxn]; 12 void init() 13 { 14 top = 0; 15 for (int i=1; i<maxn; i++) fa[i] = i, tot[i] = 1; 16 } 17 int get(int x){return x==fa[x]?x:get(fa[x]);} 18 void unions(int x, int y) 19 { 20 int fx = get(x), fy = get(y); 21 if (fx==fy) return; 22 if (tot[fx] < tot[fy]) std::swap(fx, fy); 23 fa[fy] = fx, tot[fx] += tot[fy]; 24 stk[++top] = fy; 25 } 26 void back(int t) 27 { 28 while (top > t) 29 { 30 int x = stk[top--]; 31 tot[fa[x]] -= tot[x], fa[x] = x; 32 } 33 } 34 }f; 35 int ans[maxm]; 36 int n,m; 37 int edgeTot,edges[maxm],nxt[maxm],head[maxm]; 38 39 inline int read() 40 { 41 char ch = getchar(); 42 int num = 0; 43 bool fl = 0; 44 for (; !isdigit(ch); ch = getchar()) 45 if (ch=='-') fl = 1; 46 for (; isdigit(ch); ch = getchar()) 47 num = (num<<1)+(num<<3)+ch-48; 48 if (fl) num = -num; 49 return num; 50 } 51 void addedge(int u, int v) 52 { 53 edges[++edgeTot] = v, nxt[edgeTot] = head[u], head[u] = edgeTot; 54 } 55 void dfs(int x) 56 { 57 int t = f.top; 58 if (q[x].opt==3) ans[x] = f.get(q[x].x)==f.get(q[x].y)?1:0; 59 if (q[x].opt==1) f.unions(q[x].x, q[x].y); 60 for (int i=head[x]; i!=-1; i=nxt[i]) 61 dfs(edges[i]); 62 f.back(t); 63 } 64 int main() 65 { 66 memset(head, -1, sizeof head); 67 n = read(), m = read(), f.init(); 68 for (int i=1; i<=m; i++) 69 { 70 q[i].opt = read(), q[i].x = read(); 71 if (q[i].opt==2) addedge(q[i].x, i); 72 else q[i].y = read(), addedge(i-1, i); 73 } 74 dfs(0); 75 for (int i=1; i<=m; i++) 76 if (q[i].opt==3) printf("%d\n",ans[i]); 77 return 0; 78 }
主席树做法
主席树是个好东西!它十分灵活,只要你想不到没有主席树做不到
粗看似乎主席树和并查集没有什么相干之处。但可以用主席树的叶子节点来维护历史版本每一个节点的父亲。
最初我奇怪主席树的非叶子节点表示的应是什么,然而转了一圈下来发现大家的做法都是把非叶节点空着?不过非叶节点的空闲也只是$logn$级别的,浪费了问题也不大。
例题
bzoj3674: 可持久化并查集加强版
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
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
题目分析
同如上的主席树做法。
关键步骤在代码里标出了。
1 #include<bits/stdc++.h> 2 const int maxn = 200035; 3 4 int n,m,fa[maxn<<5],dep[maxn<<5]; 5 int rt[maxn],tot,lc[maxn<<5],rc[maxn<<5]; //最大上界是应该要开到(m+n)logn的,不过由于修改次数不大,*32就可以了 6 7 int read() 8 { 9 char ch = getchar(); 10 int num = 0; 11 bool fl = 0; 12 for (; !isdigit(ch); ch = getchar()) 13 if (ch=='-') fl = 1; 14 for (; isdigit(ch); ch = getchar()) 15 num = (num<<1)+(num<<3)+ch-48; 16 if (fl) num = -num; 17 return num; 18 } 19 void build(int &rt, int l, int r) //初始建树 20 { 21 rt = ++tot; 22 if (l==r){ 23 fa[rt] = l; 24 return; 25 } 26 int mid = (l+r)>>1; 27 build(lc[rt], l, mid), build(rc[rt], mid+1, r); 28 } 29 void addSize(int rt, int l, int r, int x) //给同一连通块内节点深度+1(按秩合并的原因) 30 { 31 if (l==r){ 32 dep[rt]++; 33 return; 34 } 35 int mid = (l+r)>>1; 36 if (x <= mid) 37 addSize(lc[rt], l, mid, x); 38 else addSize(rc[rt], mid+1, r, x); 39 } 40 int queryFa(int rt, int l, int r, int x) //rt是历史版本标号,查询x的父亲节点 41 { 42 if (l==r) return rt; 43 int mid = (l+r)>>1; 44 if (x <= mid) return queryFa(lc[rt], l, mid, x); 45 return queryFa(rc[rt], mid+1, r, x); 46 } 47 int get(int rt, int x) //不路径压缩地查询x的父亲节点 48 { 49 int ff = queryFa(rt, 1, n, x); 50 return x==fa[ff]?ff:get(rt, fa[ff]); 51 } 52 void updateFa(int &rt, int pre, int l, int r, int x, int c) 53 { 54 rt = ++tot; //当前版本标号为rt,历史版本标号为pre;将x父亲节点改为c 55 if (l==r){ 56 fa[rt] = c, dep[rt] = dep[pre]; 57 return; 58 } 59 lc[rt] = lc[pre], rc[rt] = rc[pre]; 60 int mid = (l+r)>>1; 61 if (x <= mid) 62 updateFa(lc[rt], lc[pre], l, mid, x, c); 63 else updateFa(rc[rt], rc[pre], mid+1, r, x, c); 64 } 65 int main() 66 { 67 n = read(), m = read(); 68 build(rt[0], 1, n); 69 for (int i=1; i<=m; i++) 70 { 71 int opt = read(); 72 rt[i] = rt[i-1]; 73 if (opt==1){ 74 int aFa = get(rt[i], read()), bFa = get(rt[i], read()); 75 if (fa[aFa]==fa[bFa]) continue; 76 if (dep[aFa] > dep[bFa]) std::swap(aFa, bFa); 77 updateFa(rt[i], rt[i-1], 1, n, fa[aFa], fa[bFa]); 78 if (dep[aFa]==dep[bFa]) 79 addSize(rt[i], 1, n, fa[bFa]); 80 }else if (opt==2) rt[i] = rt[read()]; 81 else if (opt==3){ 82 int aFa = get(rt[i], read()), bFa = get(rt[i], read()); 83 if (fa[aFa]==fa[bFa]) 84 puts("1"); 85 else puts("0"); 86 } 87 } 88 return 0; 89 }
等等我突然发现好像没xor lastans就过了……?
END