变量释义
这里以求区间第k大的例子来解释可持久化线段树
#define N 200005
int a[N]; //原数列,这里假设a的范围同N
int b[N]; //去重后的数列
int root[N]; //每一个版本的根
int cnt; //每个节点的序号
int n; //数列长度
int m; //询问次数
struct node{
int lson,rson; //左右儿子
int sum;//落在当前节点的值域范围内的个数
}node[N<<5];
注释已经写得很清楚了。。。
核心流程
核心思想:如果不要求区间,固定求整个数组第k大的值,除了排序的方法,可以利用权值线段树(大材小用),权值线段树即用桶排序后的桶的数列建线段树。
那么需要可持久化,便是在权值线段树上增加新的版本。
如何增加新的版本?在上一个版本的基础上,根据新数据落在的范围,新增一条从头到尾的链,连在旧版本上。
譬如:新数据落在当前范围的左半部分,我就新增一个点,让上一个点的左儿子指向这个点,右儿子不变,即指向旧版本的右儿子。落在右半部分同理。
代码:
int insert(int l,int r,int x,int now){ //插入每一个数,并保存该版本
if(l==r){
cnt++;
node[cnt].lson=node[cnt].rson=0;
node[cnt].sum=node[now].sum+1;
return cnt;
}
int mid=(l+r)/2;
if(x<=mid){ //落在左半部分
cnt++;
int now1=cnt;
node[now1].lson=insert(l,mid,x,node[now].lson);
node[now1].rson=node[now].rson;
node[now1].sum=node[node[now1].lson].sum+node[node[now1].rson].sum;
return now1;
}
else{ //落在右半部分
cnt++;
int now1=cnt;
node[now1].rson=insert(mid+1,r,x,node[now].rson);
node[now1].lson=node[now].lson;
node[now1].sum=node[node[now1].lson].sum+node[node[now1].rson].sum;
return now1;
}
}
根据这个思想,我们遍历数组,并插入到权值线段树中,每个版本都对应了插入i个数后权值线段树的情况。
这样,当我们查询区间[l,r]的第k大数的时候,就可以让第r个版本减去第l-1个版本的值,得到[l,r]区间内的数构成的权值线段树,要取第K大的数,就可以在这个权值线段树上进行操作了(左儿子的
s
u
m
sum
sum值跟
k
k
k比较,
s
u
m
<
=
k
,
k
−
=
s
u
m
sum<=k,k-=sum
sum<=k,k−=sum走右儿子,反之走左儿子。
代码:
int query(int l,int r,int now1,int now2,int k){ //处理询问
if(l==r){
return l;
}
int mid=(l+r)/2;
int temp=node[node[now2].lson].sum-node[node[now1].lson].sum; //判断当前值域内的数与K的比较
if(temp>=k){
return query(l,mid,node[now1].lson,node[now2].lson,k);
}
else{
return query(mid+1,r,node[now1].rson,node[now2].rson,k-temp);
}
}
另外,由于
l
−
1
l-1
l−1可能有取零的情况,因此我们要建立第零个版本,也就是一棵空的线段树。
代码:
int build(int l,int r){ //先建立一个空树
if(l==r){
cnt++;
node[cnt].lson=node[cnt].rson=0;
node[cnt].sum=0;
return cnt;
}
int mid=(l+r)/2;
cnt++;
int now=cnt;
node[now].lson=build(l,mid);
node[now].rson=build(mid+1,r);
node[now].sum=node[node[now].lson].sum+node[node[now].rson].sum;
return now;
}
最后梳理一下可持久化线段树的核心流程:建立空树,插入数组,进行查询。