动态主席树(亲妈级注释)

动态主席树

本文介绍动态主席树,学习之前,你必须需要先了解静态主席树,本文只介绍相对于静态主席树多出来的部分。

动态主席树已经算是另一种数据结构了。这里以ZOJ2112为例。

首先我们需要知道主席树是一种离线数据结构,意思是我们不能一边询问一边修改或输出。所以我们把询问存储起来,这里主要是因为我们要储存改变的值,不然无法离散化,所以就离线了。这里要改变一个值,例如把2变成6,那么2的数量就要少一个,6的数量就要多一个,可我们又不能再开一个线段树,所以我们考虑将每一个节点都变成一棵树,每对他进行操作时就是对这棵树进行操作。下面给出详细代码及其注释。

#pragma GCC optimize(2)
#include <algorithm>
#include <iostream>
#include <cstdio>
#define sc(a) scanf("%d", &a)
#define _ff(i, a, b) for (int i = (a); i <= (b); ++i)
#define lowbit(x) x&(-x)
using namespace std;
//由于是离线操作,线段树最多有(N+询问的个数)棵
const int maxn = 6e4 + 5;
const int maxm = 1e4 + 5;
const int maxk = maxn * 32;
//a数组为初始数组,b数组用来离散化
int a[maxn], b[maxn];
//rt数组记录每棵主席树根节点的下标
int sum[maxk], ls[maxk], rs[maxk], rt[maxn];
int num, n, tot;
/*
    树状数组ta每个节点记录的是每棵线段树的根节点    
    ul数组记录询问区间下界的节点下移,ur数组记录询问区间上界的节点下移
*/
int ta[maxn], ul[maxn], ur[maxn];
struct Query{
    int l, r, k;
    bool flag;
}q[maxm];

void build(int &node, int l, int r) {
    node = ++tot;
    sum[node] = 0;
    if (l == r) return;
    int mid = (l + r) >> 1;
    build(ls[node], l, mid);
    build(rs[node], mid + 1, r);
}
void update(int &node, int pre, int l, int r, int pos, int val) {
    node = ++tot;
    sum[node] = sum[pre] + val;
    ls[node] = ls[pre], rs[node] = rs[pre];
    if (l == r) return;
    int mid = (l + r) >> 1;
    if (pos <= mid) update(ls[node], ls[pre], l, mid, pos, val);
    else update(rs[node], rs[pre], mid + 1, r, pos, val);
}
//传入的是在初始数组里的下标
void add(int x, int val) {
    //记录其在离散化数组里的下标
    int pos = lower_bound(b + 1, b + 1 + num, a[x]) - b;
    /*
        这里注意主席树每棵线段树的根节点的下标对应的值与初始数组a下标对应的值是一样的
        因为主席树的构建是按照初始数组的顺序构造的
    */
    for (; x <= n; x += lowbit(x)) update(ta[x], ta[x], 1, num, pos, val);
        
}
//查找x位置的前缀和,f为真代表查找上界,f为假查找下界
int getsum(int x, bool f) {
    int res = 0;
    for (; x; x -= lowbit(x)) {
        
        if (f) res += sum[ls[ur[x]]];
        else res += sum[ls[ul[x]]];
    }
    return res;
}
/*
    st代表询问区间的下界、ed代表询问区间的上界
    stpos代表第st棵线段树的根节点、edpos代表第ed棵线段树的根节点
*/
int query(int st, int ed, int stpos, int edpos, int l, int r, int pos) {
    if (l == r) return l;
    /*
        利用前缀和求区间和,分别计算权值线段树(构成主席树的每棵线段树都是一棵权值线段树)和树状数组
        只需要计算左区间里数字的个数,如果pos小于等于左区间的数字的个数,则去查找左区间,反之查找右区间,
        查找右区间时,对应的pos要减去左区间数字的个数,因为找的是整个区间的第k个数。
    */
    int tmp = sum[ls[edpos]] - sum[ls[stpos]] + getsum(ed, true) - getsum(st, false);
    int mid = (l + r) >> 1;
    if (pos <= tmp) {
        //查找的点在左区间
        //树状数组的节点跟随主席树的节点下移
        for (int i = ed; i; i -= lowbit(i)) ur[i] = ls[ur[i]];//上界下移
        for (int i = st; i; i -= lowbit(i)) ul[i] = ls[ul[i]];//下界下移
        return query(st, ed, ls[stpos], ls[edpos], l, mid, pos);
    } else {
        //查找的点在右区间
        //同上
        for (int i = ed; i; i -= lowbit(i)) ur[i] = rs[ur[i]];
        for (int i = st; i; i -= lowbit(i)) ul[i] = rs[ul[i]];
        return query(st, ed, rs[stpos], rs[edpos], mid + 1, r, pos - tmp);
    }
}
signed main() {
    // freopen("a.in", "r", stdin);
    char op[5]; int cntb, lbw, Q;
    int kase; sc(kase);
    while (kase--) {
        cntb = 0, tot = 0; sc(n), sc(Q);
        _ff(i, 1, n) {
            sc(a[i]);
            b[++cntb] = a[i];
        }
        //主席树是离线的,这也是他的局限所在。
        _ff(i, 1, Q) {
            scanf("%s", op);
            if (op[0] == 'C') {
                scanf("%d%d", &q[i].l, &q[i].r);
                q[i].flag = false;
                b[++cntb] = q[i].r;
            } else {
                scanf("%d%d%d", &q[i].l, &q[i].r, &q[i].k);
                q[i].flag = true;
            }
        }
        sort(b + 1, b + 1 + cntb);
        num = unique(b + 1, b + 1 + cntb) - b - 1;
        build(rt[0], 1, num);
        _ff(i, 1, n) {
            lbw = lower_bound(b + 1, b + 1 + num, a[i]) - b;
            update(rt[i], rt[i - 1], 1, num, lbw, 1);
        }
        _ff(i, 1, n) ta[i] = rt[0];
        _ff(i, 1, Q) {
            if (q[i].flag) {
                //flag为真代表查询
                //查询并记录查询上界和下界的根节点
                for (int j = q[i].r; j; j -= lowbit(j)) ur[j] = ta[j];
                for (int j = q[i].l - 1; j; j -= lowbit(j)) ul[j] = ta[j];
                //查询返回的是其在离散数组里的下标
                int ans = query(q[i].l - 1, q[i].r, rt[q[i].l - 1], rt[q[i].r], 1, num, q[i].k);
                printf("%d\n", b[ans]);
            } else {
                //把x变成y,相当于删去一个x,增加一个y,此消彼长
                add(q[i].l, -1);
                a[q[i].l] = q[i].r;
                add(q[i].l, 1);
            }
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值