P3919 【模板】可持久化数组(可持久化线段树/平衡树)

本文深入探讨了可持久化线段树(主席树)的原理与应用,阐述了如何通过分治思想优化空间复杂度,降低内存消耗。文章详细讲解了build、update和query函数的实现过程,以及如何在修改和查询操作中高效利用历史版本。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门

概述

算法模型:可持久化线段树(主席树)
空间复杂度:O((n+m)logn)
时间复杂度:O((n+m)logn)其中
预处理:O(nlogn)
单次修改:O(logn)
单次查询:O(logn)

1.思想

根据题目要求,我们要做的就是保存m个版本的状态,并实现查找与修改操作。

简单想法:真的开数组保存每个版本。当然会MLE(空间复杂度O(n*m))!!!

idea:我们注意到,对于修改操作,当前版本与它的前驱版本相比,只更改了一个节点的值,其他大多数节点的值没有变化。

能不能重复利用,以达到节省空间的目的?

——分治?没错,如果只修改了左半边,那么我们可以使用前驱版本的右半边,反之同理。(参见楼下某大佬的图)

于是,我们就可以用线段树,进行修改操作时,只要当前节点的左(右)儿子没有被修改,我们就可以使用前驱版本的那个节点。

那查找呢?每次保存版本i的根节点,利用线段树的方法查找就好了。

2.模型

build 函数:
int build(int l,int r)

建立以【l,r】为区间的(初始)线段树。

update 函数:
int update(int pre,int l,int r,int &x,int &c)

对第 pre 版本的【l,r】区间递归查找并修改第 x 位置的值为 c 。

query 函数:
void query(int pre,int l,int r,int& x)

对第 pre 版本的【l,r】区间递归查找第 x 位置的值。

3.代码实现

#include<iostream>
#include<cstdio>
#include<algorithm>
#define mid ((l+r)>>1)
using namespace std;
struct chairman{
    //参见空间复杂度,要开大数组
    int rt[1000001],T[20000001],L[20000001],R[20000001];
    int cnt;//尾节点,插入节点用
    int build(int l,int r){
        int root=++cnt;
        if(l==r){
            scanf("%d",&T[root]);return root;
            //建树与读入合二为一
        }
        //递归建子区间
        L[root]=build(l,mid);R[root]=build(mid+1,r);
        return root;
    }
    int update(int pre,int l,int r,int &x,int &c){
        int root=++cnt;
        if(l==r){
            T[root]=c;return root;//修改
        }
        L[root]=L[pre];R[root]=R[pre];//先把子节点指向前驱结点以备复用
        //递归修改子区间
        if(x<=mid)L[root]=update(L[pre],l,mid,x,c);
        else R[root]=update(R[pre],mid+1,r,x,c);
        return root;
    }
    void query(int pre,int l,int r,int& x){
        //普通的线段树查询
        if(l==r){
            printf("%d\n",T[pre]);return;
        }
        if(x<=mid)query(L[pre],l,mid,x);
        else query(R[pre],mid+1,r,x);
    }
}hsf;
int main(){
    hsf.cnt=0;
    int n,m;
    scanf("%d%d",&n,&m);
    hsf.build(1,n);
    int v,cd,x,y;
    hsf.rt[0]=1;//第零版本
    for(int i=1;i<=m;++i){
        scanf("%d%d%d",&v,&cd,&x);
        if(cd==1){
            scanf("%d",&y);
            hsf.rt[i]=hsf.update(hsf.rt[v],1,n,x,y);//修改
        }
        if(cd==2){
            hsf.rt[i]=hsf.rt[v];
            hsf.query(hsf.rt[v],1,n,x);//查询
        }
    }
    return 0;
}

4.总结

可见,可持久化线段树不仅保持了线段树良好的修改/查询方式,也利用了分治思想,降低了空间复杂度,提高了效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值