版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34283998/article/details/79320963
可持久化线段树
个人认为大部分数据结构转变为可持久化都是比较简单的,只需要保存历史版本的信息即可。在每次进行更改操作时,先复制的到之前的一个版本,然后对要进行更改的节点,复制一个出来改。并且将它的父亲指向新复制出来的点。
也就是说,我们只是在原来的历史版本上加边加点,并没有重新建一个树。
除此之外我觉得还可以有一个大胆的搞法。历史版本我们可以不用线性的结构保存,我们可以用树状数组来快速实现。这个玩意是可以实现的,但是,实现起来非常恶心。即使如此,我觉得还是有必要的写一写的。
代码是CQBZ-2111(区间查询不带修改操作)
区间查询操作就是有历史版本提供基础的。对于区间[l,r]我们只需要询问第r个和第l-1个历史版本就可以。
int ncnt,n,m;
int a[MAXN+5],b[MAXN+5],root[MAXN+5];//root中保存了历史版本
struct node
{
int cnt;
int ch[2];
}tree[MAXN*20];
void add(int &cur,int x,int l,int r)//cur当前节点的编号,x要插入的数,l、r是区间
{
tree[++ncnt]=tree[cur];//复制之前的版本
cur=ncnt;
tree[cur].cnt++;//插入时只是计数,没有保存权值
if(l==r)
return;
int mid=(l+r)>>1;
if(x<=mid)
add(tree[cur].ch[0],x,l,mid);
else
add(tree[cur].ch[1],x,mid+1,r);
}
int FindKth(int x,int y,int k,int l,int r)
{
if(l==r)
return l;
int lx=tree[x].ch[0],ly=tree[y].ch[0];
int t=tree[lx].cnt-tree[ly].cnt;//统计l到r的历史版本中,在此区间内的元素个数
int mid=(l+r)>>1;
if(k<=t)
return FindKth(lx,ly,k,l,mid);
return FindKth(tree[x].ch[1],tree[y].ch[1],k-t,mid+1,r);//注意这里的k要减去前面数的个数
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[i]=a[i];
}
sort(b+1,b+n+1);
int sum=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;i++)
{
root[i]=root[i-1];
int pos=lower_bound(b+1,b+n+1,a[i])-b;
add(root[i],pos,1,sum);///此处的pos是离散后的数!
}
for(int i=1;i<=m;i++)
{
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
int pos=FindKth(root[r],root[l-1],k,1,sum);
printf("%d\n",b[pos]);
}
}
另一道题是带有单点修改操作的题CQBZ-2133
这道题中我们就必须用树状数组减少时间复杂度。
#define MAXN 500000///在内存可以的情况下多开点
#define MAXQ 10000
int n,m,ncnt,sum;
int root[MAXN+5],a[MAXN+5],b[MAXN+5],pos[MAXN+5];
int L[MAXN+5],R[MAXN+5],cntL,cntR;
struct node
{
int cnt;
int ch[2];
}tree[MAXN*20];
void SegAdd(int &cur,int x,int l,int r,int d)//在线段树中进行加入操作
{
if(!cur)
{
ncnt++;
tree[ncnt]=tree[cur];
cur=ncnt;
}
tree[cur].cnt+=d;
if(l==r)
return;
int mid=(l+r)>>1;
if(x<=mid)
SegAdd(tree[cur].ch[0],x,l,mid,d);
else
SegAdd(tree[cur].ch[1],x,mid+1,r,d);
}
void BitAdd(int x,int pos,int d)
{
while(x<=n)
{
SegAdd(root[x],pos,1,sum,d);
x+=x&(-x);
}
}
///对于在线段树上的查询操作,因为首先在树状数组上弄出的历史版本,分别存在L和R里
///然后分别统计出在这两个区间里的个数,依次查询
int SegKth(int l,int r,int k)
{
if(l==r)
return l;
int cnt=0;
for(int i=1;i<=cntL;i++)
cnt-=tree[tree[L[i]].ch[0]].cnt;
for(int i=1;i<=cntR;i++)
cnt+=tree[tree[R[i]].ch[0]].cnt;
int mid=(l+r)>>1;
if(k<=cnt)
{
for(int i=1;i<=cntL;i++)
L[i]=tree[L[i]].ch[0];
for(int i=1;i<=cntR;i++)
R[i]=tree[R[i]].ch[0];
return SegKth(l,mid,k);
}
else
{
for(int i=1;i<=cntL;i++)
L[i]=tree[L[i]].ch[1];
for(int i=1;i<=cntR;i++)
R[i]=tree[R[i]].ch[1];
return SegKth(mid+1,r,k-cnt);
}
}
int BitKth(int x,int y,int k)
{
cntL=cntR=0;
while(x>0)
{
L[++cntL]=root[x];
x-=x&(-x);
}
while(y>0)
{
R[++cntR]=root[y];
y-=y&(-y);
}
return SegKth(1,sum,k);
}
struct Q
{
int how;
int l,r,k;
}Query[MAXQ+5];
void solve()
{
for(int i=1;i<=n;i++)
{
pos[i]=lower_bound(b+1,b+sum+1,a[i])-b;///pos里面存的是a[i]在b中得排名
BitAdd(i,pos[i],1);
}
for(int i=1;i<=m;i++)
{
int how=Query[i].how;
if(how==1)
{
int st=Query[i].l,ed=Query[i].r,k=Query[i].k;
int ans=BitKth(st-1,ed,k);
printf("%d\n",b[ans]);
}
else
{
int l=Query[i].l,k=Query[i].k;
BitAdd(l,pos[l],-1);
pos[l]=lower_bound(b+1,b+sum+1,k)-b;
BitAdd(l,pos[l],1);
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[i]=a[i];
}
sum=n;
char s[15];
for(int i=1;i<=m;i++)
{
scanf("%s",s);
if(s[0]=='Q')
{
Query[i].how=1;
scanf("%d%d%d",&Query[i].l,&Query[i].r,&Query[i].k);
}
else
{
Query[i].how=2;
scanf("%d%d",&Query[i].l,&Query[i].k);
b[++sum]=Query[i].k;
}
}
sort(b+1,b+sum+1);
sum=unique(b+1,b+sum+1)-b-1;
solve();
}