既然看到这个题了,相信静态主席树求区间第k大应该不成问题了, 此题在原有基础上,不过添加了单点修改的要求。
那么来说一下大体思路:
首先,用题目给的n个原始数据像之前一样建立主席树。对于修改,我们不是要真的去修改我们已经建立好的主席树,而是用另一个数组来维护修改所造成的变化,例如:要求 i 位置到 j 位置的第k大,之前的策略是找到第 i-1 和 第 j 棵树,两个权值线段树根据左右子节点的权值下搜,那么现在添加了修改操作,我们就必须考虑 i-1 和 j 树之间的变化,把变化也加入其中,就可以了。
原树的数组中第 i 棵树维护的是前 i 个位置的权值线段树,修改第 i 位置,对 i ~ n 的树有影响,那么,假设说我们再设 n 个权值线段树,每棵树来表示第 i 个位置上的造成的修改,如果我们要求第 i 个位置修改后正确的权值线段树信息,就需要统计1 ~ i 位置的全部修改,所以我们可以用树状数组的思想来维护这个权值线段树数组。
void add(int i, int x, int val){//第i棵权值线段树上,x位置,加val
while(i<=n){
updata(s[i], s[i], 1, tot, x, val);
i += lowbit(i);
}
}
然后是怎么查找的问题。假如说现在要查找 i+1 位置和 j 位置之间的第k大,我们要不断下移结点,直到节点表示的范围长度为 1 。开始时结点范围是 1 ~ tot, 对原主席树 i+1 树和 j 树该结点的左结点权值做差,然后取用树状数组 s ,求出前 i+1 树和 j 树当前结点的的左结点权值的变化的总和做差,再和前者相加,得到正确的左结点权值,和 k 比较后左移或右移。在此期间的问题是怎么找到接下来 s 数组中第 i+1 和 j 树的当前结点。所以我们用再开一个一维数组 use 来维护,s 中的第 x 个树到了当前深度的结点的编号 use[x]。
int get(int x){//到了当前深度,前x课树的对应结点的左结点变化总和
int ret=0;
while(x>0){
ret += sum[ L[ use[x] ] ];
x -= lowbit(x);
}
return ret;
}
整个过程比较难懂的就是利用 s 数组来查找当前结点的变化,顺着代码慢慢看一遍就好了,注释写的挺全的
Code:
#include <cstdio>
#include <algorithm>
#include <cstring>
typedef long long LL;
using namespace std;
const int maxn=6e4+500;
const int maxm=1e4+500;
int T[maxn], s[maxn]/*维护变化的主席树树状数组*/, L[maxn*32], R[maxn*32], sum[maxn*32];
int cur/*主席树加点*/, tot/*离散化数组长度*/, n;
int data[maxn];//原始数据
int num[maxn];//离散化
int use[maxn];//! 对于s,第i棵树,到目前为止,所在深度,对应的节点
//! 概况来讲,use随着静态主席树的下潜而更新
struct Q{
int a, b, k;
bool tp;//1问 0改
}q[maxm];
int gs(int x){return lower_bound(num+1, num+1+tot, x)-num;}
void build(int& rt, int l, int r){
rt = ++cur;
sum[rt] = 0;
if(l==r) return;
int mid=(l+r)>>1;
build(L[rt], l, mid);
build(R[rt], mid+1, r);
}
void updata(int &rt, int pre, int l, int r, int x, int val){
rt = ++cur;
L[rt] = L[pre], R[rt] = R[pre];
sum[rt] = sum[pre] + val;
if(l==r) return;
int mid=(l+r)>>1;
if(x<=mid) updata(L[rt], L[pre], l, mid, x, val);
else updata(R[rt], R[pre], mid+1, r, x, val);
}
int lowbit(int x) {return x&(-x); }
void add(int i, int x, int val){
while(i<=n){
updata(s[i], s[i], 1, tot, x, val);
i += lowbit(i);
}
}
int get(int x){//前x课树的变化总和(当前深度的节点)
int ret=0;
while(x>0){
ret += sum[ L[ use[x] ] ];
x -= lowbit(x);
}
return ret;
}
int query(int p1, int p2, int t1, int t2, int l, int r, int k){
if(l==r) return l;
int mid=(l+r)>>1;
int sz=get(p2) - get(p1) + sum[L[t2]] - sum[L[t1]];//左区间的权值
if(k<=sz){
for(int i=p1; i>0; i-=lowbit(i)) use[i] = L[use[i]]; //下潜
for(int i=p2; i>0; i-=lowbit(i)) use[i] = L[use[i]];
return query(p1, p2, L[t1], L[t2], l, mid, k);
}else{
for(int i=p1; i>0; i-=lowbit(i)) use[i] = R[use[i]]; //下潜
for(int i=p2; i>0; i-=lowbit(i)) use[i] = R[use[i]];
return query(p1, p2, R[t1], R[t2], mid+1, r, k-sz);
}
}
int main()
{
char cmd[5];
int t, m;
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
cur = tot = 0;//! init
for(int i=1; i<=n; i++){
scanf("%d",&data[i]);
num[++tot] = data[i];
}
for(int i=1; i<=m; i++){
scanf("%s",cmd);
if(cmd[0]=='Q'){
scanf("%d%d%d",&q[i].a, &q[i].b, &q[i].k);
q[i].tp = 1;
}
if(cmd[0]=='C'){
scanf("%d%d",&q[i].a, &q[i].b);
q[i].tp = 0;
num[++tot] = q[i].b;
}
}
sort(num+1, num+1+tot);
tot = unique(num+1, num+1+tot) - (num+1);
build(T[0], 1, tot);
for(int i=1; i<=n; i++) updata(T[i], T[i-1], 1, tot, gs(data[i]) , 1);//静态树建立完成
for(int i=1; i<=n; i++) s[i] = T[0];
for(int i=1; i<=m; i++){
if(q[i].tp==1){//询问
for(int j=q[i].a-1; j>0; j-=lowbit(j)) use[j] = s[j];
for(int j=q[i].b; j>0; j-=lowbit(j)) use[j] = s[j];//s数组准备同步下潜
printf("%d\n", num[ query(q[i].a-1, q[i].b, T[q[i].a-1], T[q[i].b], 1, tot, q[i].k) ] );
}
if(q[i].tp==0){//修改
add(q[i].a, gs( data[q[i].a] ), -1);
add(q[i].a, gs( q[i].b ), 1);
data[q[i].a] = q[i].b;
}
}
}
}