数据结构 树状数组套主席树 结构详解

本文详细解析了树状数组套主席树的数据结构,重点阐述了如何利用该结构解决静态区间第k大问题,并支持动态区间修改。通过树状数组的逐级更新实现动态修改,结合主席树的查询特性,实现了高效区间查询。此外,还提供了一段完整的C++代码示例来说明其实现过程。
摘要由CSDN通过智能技术生成

数据结构 树状数组套主席树 结构详解

😊 | Powered By HeartFireY

一、简述

主席树主要用来解决静态区间第 k k k大的问题,它本身并不支持动态修改,也就是不支持动态区间修改。

如果我们需要进行动态区间修改,那么就需要一些辅助的数据结构进行修改。这里就需要引出我们的树套树结构。本篇博客针对树状数组套主席树的结构原理进行讲解。

使用树状数组套主席树解决静态区间第 k k k大的问题,其基本思想是在根节点记录数组上建立树状数组,使得树状数组的每个节点都表示一棵主席树。那么通过树状数组就可以找到对应需要修改的主席树并对节点进行修改。

二、详解

1.结构理解

首先来看一个主席树的结构图:

在这里插入图片描述

我们在根节点上建立树状数组:

在这里插入图片描述
那么实际上存放根节点编号的 r o o t root root数组就是一个树状数组。注意上图并没有表示出实际的结构,仅是方便理解。

这里需要说明的是:所谓的在根节点上建立树状数组,实际上是嵌套在主席树建树外层的,也就是说我们会按照树状数组的的过程去执行主席树的更新操作。建树操作可以描述如下:

for(int i = 1; i <= n; i++){
    cin >> a[i];
    for(int j = i; j <= n; j += lowbit(j)) update(root[j], 0, maxn, a[i], 1);
}

那么最终我们可以得到一棵树状数组嵌套主席树的结构。那么树状数组是怎样执行动态区间修改的的呢?

2.区间修改

考虑树状数组的每个点管辖范围的确定方式,我们可以通过 l o w b i t lowbit lowbit函数逐级计算出跟某个点相关的节点。因此对于修改操作,我们只需要沿着树状数组逐级向上,将跟待修改节点有关的树上的原有值去掉(由于是权值数组,因此直接执行与建树更新相反的方式 u p d a t e ( a [ i ] o l d , − 1 ) update(a[i]_{old},-1) update(a[i]old,1)),然后再插入新值 u p d a t e ( a [ i ] n e w , 1 ) update(a[i]_{new}, 1) update(a[i]new,1)即可。

for(int i = u; i <= n; i += lowbit(i)) update(root[i], 0, maxn, a[u], -1);
a[u] = v;
for(int i = u; i <= n; i += lowbit(i)) update(root[i], 0, maxn, a[u], 1);

3.区间第 k k k大查询

普通主席树的区间第 k k k大需要两个根节点,这两个根节点分别管辖一棵拥有 [ 1 , n ] [1,n] [1,n]信息的主席树。而对于树状数组套主席树,我们则需要两组根节点,分别是与 Q L , Q R QL,QR QL,QR(待查节点)相关的节点。因此我们对于待查区间,需要先将两组根节点取出,然后再进行查询:

int cnt1 = 0, cnt2 = 0, L[amxn], R[maxn];
for(int i = u - 1; i; i -= lowbit(i))  L[++cnt1] = root[i];
for(int i = v; i; i -= lowbit(i)) R[++cnt2] = root[i];

查询时,将原来区间查询的前缀和相减计算步骤改为树状数组查询再相减即可。额外注意,由于树状数组的嵌套,每次递归前要更新 L , R L,R L,R数组的节点信息。

int query(int l, int r, int k){
    if(l == r) return l;
    int cur = 0, mid = l + r >> 1;
    for(int i = 1; i <= cnt1; i++) cur -= sum[lc[L[i]]];
    for(int i = 1; i <= cnt2; i++) cur += sum[lc[R[i]]];
    if(cur >= k){
        for(int i = 1; i <= cnt1; i++) L[i] = lc[L[i]];
        for(int i = 1; i <= cnt2; i++) R[i] = lc[R[i]];
        return query(l, mid, k);
    }
    else{
        for(int i = 1; i <= cnt1; i++) L[i] = rc[L[i]];
        for(int i = 1; i <= cnt2; i++) R[i] = rc[R[i]];
        return query(mid + 1, r, k - cur);
    }
}

三、板子

#include <bits/stdc++.h>
using namespace std;

const int N = 1e4 + 10, maxn = 1000000000;


int a[N], n, m;
int root[N], lc[N << 8], rc[N << 8], sum[N << 8], num;
int L[N], R[N], cnt1, cnt2;

inline int lowbit(int x){ return x & (-x); }

void update(int &rt, int l,int r, int x, int v){
    if(!rt) rt = ++num;
    sum[rt] += v;
    if(l == r) return;
    int mid = l + r >> 1;
    if(x <= mid) update(lc[rt], l, mid, x, v);
    else update(rc[rt], mid + 1, r, x, v);
}

int query(int l, int r, int k){
    if(l == r) return l;
    int cur = 0, mid = l + r >> 1;
    for(int i = 1; i <= cnt1; i++) cur -= sum[lc[L[i]]];
    for(int i = 1; i <= cnt2; i++) cur += sum[lc[R[i]]];
    if(cur >= k){
        for(int i = 1; i <= cnt1; i++) L[i] = lc[L[i]];
        for(int i = 1; i <= cnt2; i++) R[i] = lc[R[i]];
        return query(l, mid, k);
    }
    else{
        for(int i = 1; i <= cnt1; i++) L[i] = rc[L[i]];
        for(int i = 1; i <= cnt2; i++) R[i] = rc[R[i]];
        return query(mid + 1, r, k - cur);
    }
}

signed main(){
    ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
        for(int j = i; j <= n; j += lowbit(j)) 
            update(root[j], 0, maxn, a[i], 1);
    }
    for(int i = 1; i <= m; i++){
        char op;
        int u, v, k;
        cin >> op >> u >> v;
        if(op == 'Q'){
            cin >> k;
            cnt1 = cnt2 = 0;
            for(int i = u - 1; i; i -= lowbit(i))  L[++cnt1] = root[i];
            for(int i = v; i; i -= lowbit(i)) R[++cnt2] = root[i];
            cout << query(0, maxn, k) << endl;
        }
        else{
            for(int i = u; i <= n; i += lowbit(i)) update(root[i], 0, maxn, a[u], -1);
            a[u] = v;
            for(int i = u; i <= n; i += lowbit(i)) update(root[i], 0, maxn, a[u], 1);
        }
    }
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HeartFireY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值