数据结构 树状数组套主席树 结构详解
😊 | 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;
}