zoukankan      html  css  js  c++  java
  • 初涉可持久化并查集

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

    什么是可持久化并查集

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

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

  • 相关阅读:
    EM算法
    最大熵模型中的对数似然函数的解释
    PySpark 自定义函数 UDF
    PySpark 自定义聚合函数 UDAF
    Numpy总结
    Examples
    Examples
    Examples
    Examples
    Examples
  • 原文地址:https://www.cnblogs.com/antiquality/p/9427175.html
Copyright © 2011-2022 走看看