写法①
动态的区间主席树就是利用了树状数组的思想,对树状数组的每个节点都开了一颗权值线段树,比如说i位置开的权值线段树维护的是i-lowbit(i)+1到i这个区间的信息。
更新操作,假如更新i位置的值,如果是静态主席树那样i位置的权值线段树维护的是1到i的信息,那么就要把i到n位置的权值线段树都更新一遍,单次更新复杂度是nlog(n+m),动态主席树的话只需要把维护区间包含i位置的节点对应的权值线段树更新即可,总共有logn棵,然后每次更新都要更新到叶子节点,是log(n+m)的复杂度,因此单次更新的复杂度是lognlog(n+m)。值得注意的是更新的过程中是动态开点的也就是一个节点如果要用就开,不用就不开。因此空间复杂度是nlognlog(n+m),注意这里并没用可持久化(部分重建)的思想,也就是root[i]在root[i-1]的基础上新建lognn个点,而是root[i]和root[i-1]是相互独立的,但是用了动态开点进行了空间上的优化,所以静态主席树的空间复杂度是nlog(n+m),动态主席树是nlognlog(n+m)。
查询操作,假如查询L~R区间第k大,如果是静态主席树的话,只需用root[R]-root[L-1]即可得到L~R区间的信息,然后按照权值线段树那样如果左儿子的值>=k就去左儿子,否则去右儿子,一直到叶子节点为止,单次查询复杂度是log(n+m),动态主席树的话因为我们的建树方法同静态主席树不同所以root[R]这个权值线段树维护的区间也不同,然后由建树方式可知1~R区间 信息可以由R ,R-lowbit(R),R-lowbit(R)-lowbit(R-lowbit(R)).....,这些位置的权值线段树得到,总共有logn棵,节点存t2里。1~L-1区间的信息同理,节点存t1里。t2数组的信息-t1数组的信息就得到了L~R区间的信息,然后就和权值线段树的查询差不多了,用t2数组里节点的左儿子值减去t1数组里节点的左儿子值即可得到L~R区间的数在l,mid范围的个数,然后看这个值和k谁大谁小,大的话t1,t2数组里的节点同时往左儿子跳,否则往右儿子跳,单次查询复杂度是lognlog(n+m)。
另外在查询的时候可能会用到一些不存在的节点的值,比如说i节点存在,i的左儿子不存在,说明在i节点所在的这颗权值线段树维护的区间里,在值域区间l~mid里的数的个数为0,然后我们现在需要用val[ls[i]],但是i节点的左儿子是不存在的(没用建),那就默认ls[i]是0,然后val[ls[i]]也是0,跟我们想要的值是一样的,所以查询用不存在的节点的值是可以的没错的。
可以看到动态主席树牺牲了查询的复杂度换来了更优的更新复杂度,在两者之间达到了一个平衡。
时间复杂度 nlognlog(n+m)+m*lognlog(n+m) 空间复杂度nlognlog(n+m) (维护的是值域范围是1到n+m)
#include<bits/stdc++.h>
using namespace std;
#define fuck(x) cout<<#x<<" "<<x<<endl;
const int maxn=1e5+10;
int a[maxn],v[maxn*2],aa[maxn],bb[maxn],cc[maxn],val[maxn*400],ls[maxn*400],rs[maxn*400],root[maxn],t1[maxn],t2[maxn],t1sz,t2sz,n,m,tot,nn;
inline void update(int &rt,int l,int r,int pos,int c)
{
if(!rt) rt=++tot;
if(l==r)
{
val[rt]+=c;
return ;
}
int mid=(l+r)>>1;
if(pos<=mid)
update(ls[rt],l,mid,pos,c);
else
update(rs[rt],mid+1,r,pos,c);
val[rt]=val[ls[rt]]+val[rs[rt]];
}
inline void preupdate(int pos,int c)
{
int lsz=lower_bound(v+1,v+nn+1,a[pos])-v;
for(int i=pos;i<=n;i+=(i&-i))
update(root[i],1,nn,lsz,c);
}
inline int query(int l,int r,int k)
{
if(l==r)
return l;
int tmp=0,mid;
mid=(l+r)>>1;
for(int i=1;i<=t2sz;++i)
tmp+=val[ls[t2[i]]];
for(int i=1;i<=t1sz;++i)
tmp-=val[ls[t1[i]]];
if(tmp>=k)
{
for(int i=1;i<=t2sz;++i)
t2[i]=ls[t2[i]];
for(int i=1;i<=t1sz;++i)
t1[i]=ls[t1[i]];
return query(l,mid,k);
}
else
{
for(int i=1;i<=t2sz;++i)
t2[i]=rs[t2[i]];
for(int i=1;i<=t1sz;++i)
t1[i]=rs[t1[i]];
return query(mid+1,r,k-tmp);
}
}
inline int prequery(int l,int r,int k)
{
t1sz=t2sz=0;
for(int i=l-1;i>=1;i-=(i&-i))
t1[++t1sz]=root[i];
for(int i=r;i>=1;i-=(i&-i))
t2[++t2sz]=root[i];
return query(1,nn,k);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
v[++nn]=a[i];
}
for(int i=1;i<=m;++i)
{
char ch;
scanf(" %c",&ch);
if(ch=='Q')
scanf("%d%d%d",&aa[i],&bb[i],&cc[i]);
else
scanf("%d%d",&aa[i],&bb[i]),v[++nn]=bb[i];
}
sort(v+1,v+nn+1);
nn=unique(v+1,v+nn+1)-v-1;
for(int i=1;i<=n;++i) //建主席树
preupdate(i,1);
for(int i=1;i<=m;++i)
if(!cc[i])
preupdate(aa[i],-1),a[aa[i]]=bb[i],preupdate(aa[i],1);
else
printf("%d
",v[prequery(aa[i],bb[i],cc[i])]);
return 0;
}
写法②
利用可持久化的思想先对1到n区间建静态主席树,之后再用树状数组维护修改值,那么1到i区间的信息==root【i】+s[i]+s[i-lowbit(i)]+...+s[1],其他的就和第一种写法差不都了,时间复杂度是nlog(n+m)+mlognlog(n+m),空间复杂度是nlog(n+m)+mlognlog(n+m),第二种写法更优,由于不卡内存,我就随便开了。
#include<bits/stdc++.h>
using namespace std;
#define fuck(x) cout<<#x<<" "<<x<<endl;
const int maxn=1e5+10;
int a[maxn],v[maxn*2],aa[maxn],bb[maxn],cc[maxn],val[maxn*400],ls[maxn*400],rs[maxn*400],root[maxn],s[maxn],t1[maxn],t2[maxn],t1sz,t2sz,n,m,tot,nn;
void build(int &rt,int l,int r)
{
rt=++tot;
val[rt]=0;
if(l==r) return ;
int mid=(l+r)>>1;
build(ls[rt],l,mid);
build(rs[rt],mid+1,r);
}
void update(int &rt,int last,int l,int r,int pos,int c)
{
rt=++tot;
ls[rt]=ls[last];
rs[rt]=rs[last];
val[rt]=val[last]+c;
if(l==r) return ;
int mid=(l+r)>>1;
if(pos<=mid)
update(ls[rt],ls[last],l,mid,pos,c);
else
update(rs[rt],rs[last],mid+1,r,pos,c);
}
void add(int x,int c)
{
int pos=lower_bound(v+1,v+nn+1,a[x])-v;
while(x<=n)
{
update(s[x],s[x],1,nn,pos,c);
x+=x&-x;
}
}
inline int query(int rt1,int rt2,int l,int r,int k)
{
if(l==r)
return l;
int tmp=val[ls[rt2]]-val[ls[rt1]],mid;
mid=(l+r)>>1;
for(int i=1;i<=t2sz;++i)
tmp+=val[ls[t2[i]]];
for(int i=1;i<=t1sz;++i)
tmp-=val[ls[t1[i]]];
if(tmp>=k)
{
for(int i=1;i<=t2sz;++i)
t2[i]=ls[t2[i]];
for(int i=1;i<=t1sz;++i)
t1[i]=ls[t1[i]];
return query(ls[rt1],ls[rt2],l,mid,k);
}
else
{
for(int i=1;i<=t2sz;++i)
t2[i]=rs[t2[i]];
for(int i=1;i<=t1sz;++i)
t1[i]=rs[t1[i]];
return query(rs[rt1],rs[rt2],mid+1,r,k-tmp);
}
}
inline int prequery(int l,int r,int k)
{
t1sz=t2sz=0;
for(int i=l-1;i>=1;i-=(i&-i))
t1[++t1sz]=s[i];
for(int i=r;i>=1;i-=(i&-i))
t2[++t2sz]=s[i];
return query(root[l-1],root[r],1,nn,k);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
v[++nn]=a[i];
}
for(int i=1;i<=m;++i)
{
char ch;
scanf(" %c",&ch);
if(ch=='Q')
scanf("%d%d%d",&aa[i],&bb[i],&cc[i]);
else
scanf("%d%d",&aa[i],&bb[i]),v[++nn]=bb[i];
}
sort(v+1,v+nn+1);
nn=unique(v+1,v+nn+1)-v-1;
build(root[0],1,nn);
for(int i=1;i<=n;++i) //建静态主席树
update(root[i],root[i-1],1,nn,lower_bound(v+1,v+nn+1,a[i])-v,1);
for(int i=1;i<=n;i++) s[i]=root[0];
for(int i=1;i<=m;++i)
if(!cc[i])
add(aa[i],-1),a[aa[i]]=bb[i],add(aa[i],1);
else
printf("%d
",v[prequery(aa[i],bb[i],cc[i])]);
return 0;
}
为了学这个看了好多博客,但是很多博客都是说的很简略,没说到重点,下面推荐几篇大佬的博客,也当是为以后复习提供方便