BZOJ3673 可持久化并查集 by zky <可持久化数组+主席树>

可持久化并查集 by zky

n个集合 m个操作
操作:
1 a b 合并a,b所在集合
2 k 回到第k次操作之后的状态(查询算作操作)
3 a b 询问a,b是否属于同一集合,是则输出1否则输出0

Sample Input
5 6
1 1 2
3 1 2
2 0
3 1 2
2 1
3 1 2
Sample Output
1
0
1
Hint
0 < n,m <= 2*10^4

标签:主席树维护可持久化数组

本题的做法其实和并查集没太大关联。
如果是撤销,那可以用不加路径压缩的并查集完成,但是如果回到某时间,则不太好写。
这时,我们发现并查集这个东西构造其实很简单,只需要一个fa数组就行了。所以我们自然可以想到直接用一个二维数组存储每个时间的fa数组,即用增加的一维表示时间。
但是,n、m是2e4级别,所以肯定会MLE,这里我们就需要用到主席树,把fa数组可持久化。这里需要注意我们不能用路径压缩优化,因为我们需要回到前面的状态,为了让它跑得更快,我们可以用按秩合并优化。
最后附上AC代码:

#include <iostream>
#include <cstdio>
#define MAX_N 20000
using namespace std;
int n, m, cnt, now, root[MAX_N*10+5];
struct node {int fa, dep, ls, rs;} tr[MAX_N*100+5];
void build(int v, int l, int r) {
    if (l == r) {tr[v].fa = l;  return;}
    int mid = l+r>>1;
    tr[v].ls = ++cnt, tr[v].rs = ++cnt;
    build(tr[v].ls, l, mid);
    build(tr[v].rs, mid+1, r);
}
void modifyfa(int v, int o, int s, int t, int pos, int x) {
    tr[v] = tr[o];
    if (s == t) {tr[v].fa = x;  return;}
    int mid = s+t>>1;
    if (pos <= mid) modifyfa(tr[v].ls = ++cnt, tr[o].ls, s, mid, pos, x);
    else    modifyfa(tr[v].rs = ++cnt, tr[o].rs, mid+1, t, pos, x);
}
void modifydep(int v, int s, int t, int pos) {
    if (s == t) {tr[v].dep++;   return;}
    int mid = s+t>>1;
    if (pos <= mid) modifydep(tr[v].ls, s, mid, pos);
    else    modifydep(tr[v].rs, mid+1, t, pos);
}
int find(int v, int s, int t, int pos) {
    if (s == t) return v;
    int mid = s+t>>1;
    if (pos <= mid) return find(tr[v].ls, s, mid, pos);
    else    return find(tr[v].rs, mid+1, t, pos);
}
int getf(int r, int x) {
    int pos = find(r, 1, n, x);
    if (tr[pos].fa != x)    return getf(r, tr[pos].fa);
    return pos;
}
int main() {
    scanf("%d%d", &n, &m);
    cnt = 0, root[0] = ++cnt;
    build(root[0], 1, n);
    for (int now = 1; now <= m; now++) {
        int opt;
        scanf("%d", &opt);
        if (opt == 1) {
            root[now] = root[now-1];
            int a, b;
            scanf("%d%d", &a, &b);
            int posa = getf(root[now], a), posb = getf(root[now], b);
            if (tr[posa].fa == tr[posb].fa) continue;
            if (tr[posa].dep > tr[posb].dep)    swap(posa, posb);
            root[now] = ++cnt;
            modifyfa(root[now], root[now-1], 1, n, tr[posa].fa, tr[posb].fa);
            if (tr[posa].dep == tr[posb].dep)   modifydep(root[now], 1, n, tr[posb].fa);
        }
        if (opt == 2) {
            int k;
            scanf("%d", &k);
            root[now] = root[k];
        }
        if (opt == 3) {
            root[now] = root[now-1];
            int a, b;
            scanf("%d%d", &a, &b);
            if (tr[getf(root[now], a)].fa == tr[getf(root[now], b)].fa) printf("1\n");
            else    printf("0\n");
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/AzraelDeath/p/7561726.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值