要讲清楚这个问题还是需要从树状数组的原理去入手。
当时我没想明白的是为什么和静态主席树的update开点有不一样的地方,想来原因是这样的。
首先,根据网上的一些讲法和luogu的实际过题,个人认为树状数组套主席树的空间复杂度是((n+m)*logn*log(len))的,那么这题大约是17*17*2e5,但是实际上我开128倍就过了.所以还是觉得很迷。
先来复习树状数组:给你一个数列,边修改边询问,多次询问区间和,怎么做?
区间查询
ok 下面利用C[i]数组,求A数组中前i项的和
举个例子 i=7;
sum[7]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7] ; 前i项和
C[4]=A[1]+A[2]+A[3]+A[4]; C[6]=A[5]+A[6]; C[7]=A[7];
可以推出: sum[7]=C[4]+C[6]+C[7];
序号写为二进制: sum[(111)]=C[(100)]+C[(110)]+C[(111)];
再举个例子 i=5
sum[5]=A[1]+A[2]+A[3]+A[4]+A[5] ; 前i项和
C[4]=A[1]+A[2]+A[3]+A[4]; C[5]=A[5];
可以推出: sum[5]=C[4]+C[5];
序号写为二进制: sum[(101)]=C[(100)]+C[(101)];
当我们修改A[]数组中的某一个值时 应当如何更新C[]数组呢?
回想一下 区间查询的过程,再看一下上文中列出的图
结合代码分析
void add(int x,int y)
{
for(int i=x;i<=n;i+=lowbit(i))
tree[i]+=y;
}
//可以发现 更新过程是查询过程的逆过程
//由叶子结点向上更新C[]数组
如图:
当更新A[1]时 需要向上更新C[1] ,C[2],C[4],C[8]
C[1], C[2], C[4], C[8]
写为二进制 C[(001)],C[(010)],C[(100)],C[(1000)]
1(001) C[1]+=A[1]
lowbit(1)=001 1+lowbit(1)=2(010) C[2]+=A[1]
lowbit(2)=010 2+lowbit(2)=4(100) C[4]+=A[1]
lowbit(4)=100 4+lowbit(4)=8(1000) C[8]+=A[1]
通过类似二进制的方式,达到了logn修改和logn动态查询前缀和的目的。
那么回到这题,我们把一颗主席树,看成是一个点,也就是图中树状数组的最底层。
那么我们通过这个方式,就可以优化修改logn棵与要修改的位置有关的C[],不过同时最后求和的时候多付出logn的代码[但是相当划算]
特别要注意的是update的过程,在静态主席树查询的过程中,代码是这样的:每次可以新开一颗主席树,不过更新的是和前一棵有区别的logn长链,剩下的直接指回去就好了。
同时,每一次循环中都会更新,所以这样是没问题的。
但是,在本题的动态单点修改中,就有问题了。不能再这么修改。
LL update(LL pre,LL l,LL r,LL x){
LL rt=++tot;
tree[rt].l=tree[pre].l;
tree[rt].r=tree[pre].r;
tree[rt].sum=tree[pre].sum+1;
LL mid=(l+r)>>1;
if(l<r){
if(x<=mid){
tree[rt].l=update(tree[pre].l,l,mid,x);
}
else tree[rt].r=update(tree[pre].r,mid+1,r,x);
}
return rt;
}
for(LL i=1;i<=n;i++){
LL x=lower_bound(b+1,b+1+siz,a[i])-b;
root[i]=update(root[i-1],1,siz,x);
}
还是以这张图为例,我们开始的时候更新pos=1的位置,在c[1],c[2],c[4],c[8]建立主席树,当然没什么问题。每颗树的左右儿子节点对应也没问题。
但是,当更新pos=2的位置的时候,问题出现了。
更新pos=2的时候,要修改c[2],c[4].c[8]的主席树,这时候我们不能再开新的节点去覆盖,这也是和静态的不同的地方。
LL modify(LL pre,LL l,LL r,LL x,LL val){
LL rt=++tot;
tree[rt].l=tree[pre].l;tree[rt].r=tree[pre].r;
tree[rt].sum+=val;
LL mid=(l+r)>>1;
if(l<r){
if(x<=mid) tree[rt].l=modify(tree[pre].l,l,mid,x,val);
else tree[rt].r=modify(tree[pre].r,mid+1,r,x,val);
}
return rt;
}
如果仍要去这样开点的话,就会导致c[2],c[4],c[8]开成一颗新的主席树。而我们想要的是在c[2],c[4],c[8]维护加上去的信息,也就是更新原来在那的主席树的信息。
所以要改成引用,同时判断当前点是否开过,开过就代表这里是进行更新,而不是开点。
void modify(LL &now,LL l,LL r,LL x,LL val){
if(!now) now=++tot;
tree[now].sum+=val;
if(l==r) return;
LL mid=(l+r)>>1;
if(x<=mid) modify(tree[now].l,l,mid,x,val);
else modify(tree[now].r,mid+1,r,x,val);
}
剩下的参考网上的其他的博客就比较清楚了。这个博客还挺图文并茂的:https://www.cnblogs.com/TaylorSwift13/p/11228276.html
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
#define lowbit(x) x&(-x)
using namespace std;
const int maxn=1e5+100;
typedef long long LL;
struct Tree{
LL l,r,rt,sum;
}tree[maxn*4*35];
struct operation{bool b;LL l,r,k;LL pos,t;}q[maxn];
LL n,m,a[maxn],root[maxn],o[maxn*2],tot=0,len=0;
LL temp[2][30],cnt[10];
void modify(LL &now,LL l,LL r,LL x,LL val){
if(!now) now=++tot;
tree[now].sum+=val;
if(l==r) return;
LL mid=(l+r)>>1;
if(x<=mid) modify(tree[now].l,l,mid,x,val);
else modify(tree[now].r,mid+1,r,x,val);
}
void pre_modify(LL pos,LL val){
LL x=lower_bound(o+1,o+1+len,a[pos])-o;
for(LL i=pos;i<=n;i+=lowbit(i)){
modify(root[i],1,len,x,val);///处理出需要修改哪log棵主席树
}
}
LL query(LL l,LL r,LL k){
if(l==r) return l;
LL mid=(l+r)>>1;LL sum=0;
for(LL i=1;i<=cnt[1];i++) sum+=tree[tree[temp[1][i]].l].sum;
for(LL i=1;i<=cnt[0];i++) sum-=tree[tree[temp[0][i]].l].sum;
if(k<=sum){
for(LL i=1;i<=cnt[1];i++){
temp[1][i]=tree[temp[1][i]].l;
}
for(LL i=1;i<=cnt[0];i++){
temp[0][i]=tree[temp[0][i]].l;
}
return query(l,mid,k);
}
else{
for(LL i=1;i<=cnt[1];i++){
temp[1][i]=tree[temp[1][i]].r;
}
for(LL i=1;i<=cnt[0];i++){
temp[0][i]=tree[temp[0][i]].r;
}
return query(mid+1,r,k-sum);
}
}
LL pre_query(LL l,LL r,LL k){
memset(temp,0,sizeof(temp));
cnt[0]=cnt[1]=0;
for(LL i=r;i;i-=lowbit(i)) temp[1][++cnt[1]]=root[i];
for(LL i=l-1;i;i-=lowbit(i)) temp[0][++cnt[0]]=root[i];
return query(1,len,k);
}
int main(void)
{
cin.tie(0);std::ios::sync_with_stdio(false);
cin>>n>>m;
for(LL i=1;i<=n;i++){
cin>>a[i];
o[++len]=a[i];
}
for(LL i=1;i<=m;i++){
char op;cin>>op;
if(op=='Q'){
q[i].b=1; cin>>q[i].l>>q[i].r>>q[i].k;
}
else{
q[i].b=0;
cin>>q[i].pos>>q[i].t;
o[++len]=q[i].t;
}
}
sort(o+1,o+len+1);
len=unique(o+1,o+len+1)-o-1;
for(LL i=1;i<=n;i++){
pre_modify(i,1);
}
for(LL i=1;i<=m;i++){
if(q[i].b==1){
cout<<o[pre_query(q[i].l,q[i].r,q[i].k)]<<endl;
}
else{
pre_modify(q[i].pos,-1);
a[q[i].pos]=q[i].t;
pre_modify(q[i].pos,1);
}
}
return 0;
}