可持久化线段树学习笔记

可持久化线段树学习笔记

变量释义

这里以求区间第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 l1可能有取零的情况,因此我们要建立第零个版本,也就是一棵空的线段树。
代码:

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;
}

最后梳理一下可持久化线段树的核心流程:建立空树,插入数组,进行查询。

典型例题

浙江省赛E题Easy DP Problem

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值