数据结构的可持久化,就是把一个数据结构的历史状态全都保存下来,从而能够快速查找之前出现过的某个操作的结果。当然这必然会带来很大的时间和空间消耗,因此优越的可持久化都会充分利用数据结构历史状态里的相似部分来减少时间和空间复杂度。
显然有一个很裸的可持久化姿势:对数据结构中的每一个节点内嵌一个数据结构(比如平衡树或者STL里那些东西),然后以时间为关键字维护历史状态(显然会让时间复杂度里多一个log).
只看可持久化线段树和平衡树就好了吧= =
我们来学一点姿势(o゜▽゜)o☆
线段树的可持久化
显然我不能对他搞个平衡树一段段区间往里面扔…
首先查询没什么好说的,还是跟普通线段树的查询姿势一样(就是查询有了一个不同的版本而已).重要的就是修改.
简单的单点修改的话,只需要对每个节点修改前,先新建出一个节点,让这个节点记录下当前节点的状态,再对新建的节点修改就好了(๑•̀ㅂ•́)و✧对线段树来说,修改一个点要访问的节点个数不过是log(n)log(n)个而已,所以每一次修改也只会新建出log(n)log(n)个新节点.
接下来就是区间修改+标记了.
标记下放时对两个孩子节点分别建立新节点然后做单点修改的方式修改,而父节点并不需要(因为下放标记并不需要修改到他).
那标记要如何建立呢?
很简单,当前节点如果被全部覆盖,那就建一个节点修改一下打上标记就好了;如果当前节点没有被全部覆盖,那就把他的子节点里被全部覆盖的那个新建节点修改打标记.
线段树的可持久化实现起来好像很简单代码很短的样子www
可持久化平衡树
我并不想学TreapQAQ
会sbt,Splay然而都不能加可持久化233
还是滚粗吧
可持久化数据结构就是让某种数据结构可以持久的访问以前的历史版本,同时也可以从某个历史版本再建一个新的版本的数据结构,比如可持久化线段树,并查集,treap。
正常的线段树长这个样子。但是如果我对这棵树进行一些奇奇怪怪的操作那么我们就无法查询以前的版本了,比如说我们要查询区间第k大,那么我们可以通过查找树找到第k大的数,但是对于区间第k大就不行了,所以我们要把每个历史版本都存下了,很明显,直接存空间是会炸的,但是我们可以利用空间,说以我们可以把不同历史版本的相同部分利用起来,于是各个历史版本之间就会有一些共用的节点。比如我把上面这颗线段树先整成一个空树,再加一个2,就变成了下面这个图
在这里我们新加了一个树根,因此我们要把每一个历史版本的数根用root[MAXN]存下来,由于在这个问题上我们只需要把2的所有父节点建出来,因此插入第一个元素后,土红色是新生成的结点,它们是cnt都是1,其余的蓝色结点的cnt仍是0。每插入一个元素,仅新增log(n)个结点,耗时log(n),因此建树的时间、空间均为nlog(n)
那么我们要查询区间就是从两个历史版本的root开始相减。就可以像正常的一样查找了。
这个是代码。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=100000;
struct node {
int tot;
int ch[2];
};
node tree[MAXN*20];
int ncnt,root[MAXN+5];
int a[MAXN+5],b[MAXN+5];
int n,m,bn;
void init()
{
ncnt=0;
}
void insert(int &cur,int val,int l,int r)
{
ncnt++;
tree[ncnt]=tree[cur];
cur=ncnt;
tree[cur].tot++;
if(l==r)
return ;
int mid=(l+r)>>1;
if(val<=mid)
insert(tree[cur].ch[0],val,l,mid);
else
insert(tree[cur].ch[1],val,mid+1,r);
}
void prepare()
{
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);
bn=unique(b+1,b+n+1)-b-1;
root[0]=0;
for(int i=1;i<=n;i++)
{
int val=lower_bound(b+1,b+n+1,a[i])-b;
root[i]=root[i-1];
insert(root[i],val,1,bn);
}
}
int find(int x,int y,int k,int l,int r)
{
if(l==r)
return l;
int mid=(l+r)>>1;
int t=tree[tree[x].ch[0]].tot-tree[tree[y].ch[0]].tot;
if(k<=t)
return find(tree[x].ch[0],tree[y].ch[0],k,l,mid);
else
return find(tree[x].ch[1],tree[y].ch[1],k-t,mid+1,r);
}
void solve()
{
int l,r,k;
for(int i=1;i<=m;i++)
{
scanf("%d %d %d",&l,&r,&k);
printf("%d\n",b[find(root[r],root[l-1],k,1,bn)]);
}
}
int main()
{
init();
prepare();
solve();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
不过这是不带修改的,带修改的由于要影响以前的n个版本,如果暴力更新,时间就会直接炸掉,所以我们用树套树的的办法来解决,即再加一个树状数组来维护序列的前缀和。
这是带修改的。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=500005,MAXQ=100005;
struct node
{
int tot;
int ch[2];
};
node tree[MAXN*20];
int root[MAXN];
int ncnt;
int a[MAXN],b[MAXN*2];
int n,m,bn;
int cntL,cntR,L[MAXN],R[MAXN];
int pos[MAXN];
struct cmd
{
int l,r,k;
char op;
}query[MAXQ];
void SegUpdate(int &cur,int l,int r,int pos,int d)
{
if(!cur)
{
ncnt++;
tree[ncnt]=tree[cur];
cur=ncnt;
}
tree[cur].tot+=d;
if(l==r)
return ;
int mid=(l+r)>>1;
if(pos<=mid)
SegUpdate(tree[cur].ch[0],l,mid,pos,d);
else
SegUpdate(tree[cur].ch[1],mid+1,r,pos,d);
}
void BitUpdate(int x,int pos,int d)
{
while(x<=n)
{
SegUpdate(root[x],1,bn,pos,d);
x+=x&-x;
}
}
int GetPos(int x)
{
return lower_bound(b+1,b+bn+1,x)-b;
}
void prepare()
{
char s[20];
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[i]=a[i];
}
bn=n;
for(int i=1;i<=m;i++)
{
scanf("%s",s);
query[i].op=s[0];
if(s[0]=='Q')
scanf("%d %d %d",&query[i].l,&query[i].r,&query[i].k);
else
{
scanf("%d %d",&query[i].l,&query[i].k);
b[++bn]=query[i].k;
}
}
sort(b+1,b+bn+1);
bn=unique(b+1,b+bn+1)-b-1;
root[0]=0;
for(int i=1;i<=n;i++)
{
pos[i]=GetPos(a[i]);
BitUpdate(i,pos[i],1);
}
}
int SegKth(int l,int r,int k)
{
if(l==r)
return l;
int mid=(l+r)>>1;
int tl=0,tr=0,sum;
for(int i=1;i<=cntL;i++)
tl+=tree[tree[L[i]].ch[0]].tot;
for(int i=1;i<=cntR;i++)
tr+=tree[tree[R[i]].ch[0]].tot;
sum=tr-tl;
if(k<=sum)
{
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-sum);
}
}
int BitKth(int st,int ed,int k)
{
cntL=cntR=0;
while(st>0)
{
L[++cntL]=root[st];
st-=st&-st;
}
while(ed>0)
{
R[++cntR]=root[ed];
ed-=ed&-ed;
}
int pos=SegKth(1,bn,k);
return b[pos];
}
void solve(){
int st=0,ed=0,k=0,ans=0;
for(int i=1;i<=m;i++)
{
if(query[i].op=='Q')
{
st=query[i].l;
ed=query[i].r;
k=query[i].k;
ans=BitKth(st-1,ed,k);
printf("%d\n",ans);
}
else
{
st=query[i].l,k=query[i].k;
BitUpdate(st,pos[st],-1);
pos[st]=GetPos(k);
BitUpdate(st,pos[st],1);
}
}
}
int main()
{
ncnt=0;
prepare();
solve();
}