可持久性数组 bzoj3674 可持久化并查集加强版

传送门:点击打开链接

题意: 强制在线操作,定义3种操作,查询两个点是否在同一集合,合并两个点,把状态恢复到之前的某个状态

思路:利用线段树实现的可持久性数组,其实原理上就是线段树,那么是如何实现可持久性数组的呢。

对于某次修改,就再造一颗线段树,但是这样明显效率和空间都会爆炸。

因为我们其实有很多节点都是上一次剩下的,其实我只需要新建新添加的那条链上的节点即可。其他节点只需要直接接在以前的上面就行了。

然后我们如果能实现持久性数组,那么就能实现持久性并查集,持久性链表,持久性线段树,持久性平衡树等等!


对于并查集,就算不压缩路径,只是按照深度去启发式合并,效率也能保持在O(logn)以内的

然后就能搞这道题了

#include<map>
#include<set>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<cstdio>
#include<cctype>
#include<string>
#include<vector>
#include<cstring>
#include<iomanip>
#include<iostream>
#include<algorithm>
#include<functional>
#define fuck(x) cout<<"["<<x<<"]"
#define FIN freopen("input.txt","r",stdin)
#define FOUT freopen("output.txt","w+",stdout)
using namespace std;
typedef long long LL;
typedef pair<int, int>PII;

const int MX = 5e6 + 5;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;

int deep[MX], n, m;
int o[MX], ls[MX], rs[MX], s[MX], sz;
void build(int l, int r, int &rt) {
    rt = ++sz;
    if(l == r) {
        s[rt] = l; deep[rt] = 0;
        return;
    }
    int m = (l + r) >> 1;
    build(l, m, ls[rt]);
    build(m + 1, r, rs[rt]);
}
int query(int pos, int l, int r, int rt) {
    if(l == r) return rt;
    int m = (l + r) >> 1;
    if(pos <= m) return query(pos, l, m, ls[rt]);
    return query(pos, m + 1, r, rs[rt]);
}
void add(int pos, int val, int l, int r, int pr, int &rt) {
    rt = ++sz;
    if(l == r) {s[rt] = val; deep[rt] = deep[pr]; return;}
    ls[rt] = ls[pr]; rs[rt] = rs[pr];
    int m = (l + r) >> 1;
    if(pos <= m) add(pos, val, l, m, ls[pr], ls[rt]);
    else add(pos, val, m + 1, r, rs[pr], rs[rt]);
}
void update(int pos, int l, int r, int pr, int &rt) {
    rt == ++sz;
    if(l == r) {s[rt] = s[pr]; deep[rt] = deep[pr] + 1 ; return;}
    ls[rt] = ls[pr]; rs[rt] = rs[pr];
    int m = (l + r) >> 1;
    if(pos <= m) update(pos, l, m, ls[pr], ls[rt]);
    else update(pos, m + 1, r, rs[pr], rs[rt]);
}
int find(int x, int rt) {
    int p = query(x, 1, n, rt);
    if(s[p] == x) return p;
    return find(s[p], rt);
}

inline int read() {
    char c = getchar();
    while(!isdigit(c)) c = getchar();

    int x = 0;
    while(isdigit(c)) {
        x = x * 10 + c - '0';
        c = getchar();
    }
    return x;
}

int main() {
    //FIN;
    while(~scanf("%d%d", &n, &m)) {
        sz = 0;
        build(1, n, o[0]);
        int last = 0;
        for(int i = 1; i <= m; i++) {
            int op, a, b;
            o[i] = o[i - 1];
            op = read(); a = read();
            a ^= last;
            if(op == 1) {
                b = read(); b ^= last;
                a = find(a, o[i]), b = find(b, o[i]);
                if(s[a] == s[b]) continue;
                if(deep[a] > deep[b]) swap(a, b);
                add(s[a], s[b], 1, n, o[i - 1], o[i]);
                if(deep[a] == deep[b]) update(s[b], 1, n, o[i], o[i]);
            } else if(op == 2) o[i] = o[a];
            else {
                b = read(); b ^= last;
                a = find(a, o[i]), b = find(b, o[i]);
                last = (a == b);
                printf("%d\n", last);
            }
        }
    }
    return 0;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值