可持久化线段树【主席树】可持久化并查集【主席树+并查集】

笼统的主席树原理

众所周知, 主席树是可以持久化的, 换言之你能知道你所维护信息的所有历史状态。 主席树是这样做的:

1.

首先建一颗朴素的线段树,代表初始状态 (下图黑色) , 也就是第0次操作后的状态。

tipA:

你每次只对一个叶子节点的数据进行更新,所以相当于更改了树上的一条链。

2.

我们不在原来的树上修改,而是创建若干个新的节点组成一条链代表树中修改后的各个节点 (下图红色) ,然后直接把这条链糊到树上, 并且让新链中的每个节点都连好它在树中应该连的节点 (下图蓝色)

tipB:

你会发现只要交换新旧链, 就可以得到两颗完整的树

tipC:

你还会发现:在每次更新中添加的新链,都会包含树的根节点,所以你只需要记录下第[0 - N]次操作后的树的根节点,就可以通过某一个根节点得到特定历史版本的树。
(0次操作后的根节点是黑色树根, 1次操作后的根节点是红色树根)

3.

然 后 随 便 van van , 主 席 树 就 学 完 了。

4.

然后我们就可以滚到别的题解上爬了
在这里插入图片描述

主席树 luogu.com.cn 的主席树板子

纯区间第K大
区间[r - (l - 1)] 相当于只插入了[l - r] 然后直接找。

#include <bits/stdc++.h>

const int N = 2e5+4;
int lst[N]/*原序列*/, srt[N]/*排序数组*/, root[N]/*记录树根*/;
int sum[N<<5], L[N<<5], R[N<<5];
int n, m, ql, qr, k, idx, sn;

int build(int l, int r){
    int rt = ++idx, mid = (l + r) >> 1;
    if(l < r){
        L[rt] = build(l, mid);
        R[rt] = build(mid + 1, r);
    }
    return rt;
}

int update(int pre, int l, int r, int k){
    int rt = ++idx, mid = (l + r) >> 1;
    L[rt] = L[pre], R[rt] = R[pre], sum[rt] = sum[pre] + 1;
    if(l < r){
        if(k <= mid) L[rt] = update(L[pre], l, mid, k);
        else R[rt] = update(R[pre], mid + 1, r, k);
    }
    return rt;
}

int query(int ql, int qr, int l, int r, int k){
    if( l >= r) return l;
    int num = sum[L[qr]] - sum[L[ql]], mid = (l + r) >> 1;
    if(num >= k) return query(L[ql], L[qr], l, mid, k);
    else return query(R[ql], R[qr], mid + 1, r, k - num);
}

int main(){
    scanf("%d%d", &n, &m);
    for(int i=1; i<=n; i++){
        scanf("%d", &lst[i]);
        srt[i] = lst[i];
    }
    std::sort(srt + 1, srt + 1 + n);
    sn = std::unique(srt + 1, srt + 1 + n) - srt - 1;
    root[0] = build(1, sn);
    for(int i=1; i<=n; i++){
        k = std::lower_bound(srt + 1, srt + 1 + sn, lst[i]) - srt;
        root[i] = update(root[i - 1], 1, sn, k);
    }
    while(m --){
        scanf("%d%d%d", &ql, &qr, &k);
        n = query(root[ql - 1], root[qr], 1, sn, k);
        printf("%d\n", srt[n]);
    }
    return 0;
}

主席树+并查集 luogu.com.cn 的可持久化并查集板子

纯并查集

#include <bits/stdc++.h>

const int N = 1e5 + 4, M = 2e5 + 4;
int n, q, idx, x, y, op, xx, yy;
int D[M << 5], F[M << 5], L[M << 5], R[M << 5], E[M];

int build(int l, int r){
    int rt = ++idx;
    if(l == r) { F[rt] = l, D[rt] = 1; return rt; }
    int mid = (l + r) >> 1;
    L[rt] = build(l, mid);
    R[rt] = build(mid + 1, r);
    return rt;
}

int update(int pre, int l, int r, int k, int f){
    int rt = ++idx;
    if(l == r){
        D[rt] = D[pre];
        F[rt] = f;
        return rt;
    }
    L[rt] = L[pre], R[rt] = R[pre];
    int mid = (l + r) >> 1;
    if(k <= mid) L[rt] = update(L[pre], l, mid, k , f);
    else R[rt] = update(R[pre], mid + 1, r, k, f);
    return rt;
}

int query(int rt, int l, int r, int k){
    if(l == r) return rt;
    int mid = (l + r) >> 1;
    if(k <= mid) return query(L[rt], l, mid, k);
    return query(R[rt], mid + 1, r, k);
}

void cd(int rt, int l, int r, int k){
    if(l == r) {D[rt] += 1; return ;}
    int mid = (l + r) >> 1;
    if(k <= mid) cd(L[rt], l, mid, k);
    else cd(R[rt], mid + 1, r, k);
}

int ffind(int rt, int x){
    int t = query(rt, 1, n, x);
    if(x == F[t]) return t;
    return ffind(rt, F[t]);
}

int main(){
    scanf("%d%d", &n, &q);
    E[0] = build(1, n);
    for(int i=1; i<=q; i++){
        scanf("%d", &op);
        if(op == 1){
            scanf("%d%d", &x, &y);
            E[i] = E[i - 1];
            xx = ffind(E[i], x), yy = ffind(E[i], y);
            if(D[xx] > D[yy]) std::swap(xx, yy);
            E[i] = update(E[i - 1], 1, n, F[xx], F[yy]);
            if(D[xx] + 1 > D[yy]) cd(E[i], 1, n, F[yy]);
        }
        if(op == 2){
            scanf("%d", &x);
            E[i] = E[x];
        }
        if(op == 3){
            scanf("%d%d", &x, &y);
            E[i] = E[i - 1];
            xx = ffind(E[i], x), yy = ffind(E[i], y);
            printf("%d\n", F[xx] == F[yy] ? 1 : 0);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值