初涉可持久化并查集

离线做法 / 并查集与主席树的结合

什么是可持久化并查集

顾名思义,就是可持久化的并查集————即支持历史版本回溯的并查集。

比如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
",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

Sample Output

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

原文地址:https://www.cnblogs.com/antiquality/p/9427175.html