动态开点线段树,线段树合并

对于传统线段树,我们都是把区间开满,然后要修改哪个区间,就去找包含那个区间的结点

这样子的话,就会占用很多的内存

而动态开点线段树,就是对于给定的区间[L,R],我一开始是一棵空的线段树,或者说有一个节点(L到R),当我需要插入某个下标为k的数的时候,我再一路创建所表示区间包含k的结点并修改(如果这个结点先前没有被创建的话),从而节省了大量的内存

实现代码如下:

const int N=2E5+10;
#define mid (l+r>>1)
//ls[u]为u的左儿子
//rs[u]为u的右儿子
//sum[u]为u的权值和
int ls[N*40],rs[N*40],sum[N*40];
//我们要开N棵这样子的线段树
int root[N],tot;

//利用左右儿子结点由下往上地更新父结点
void pushup(int u){
    sum[u]=sum[ls[u]]+sum[rs[u]];
}
//u为要修改的结点
//l,r为结点所表示的区间
//p为要查找的下标p
//k为要修改的权值
//这里为单点修改
//区间修改的类似,要修改区间[x,y]的话,把int p换成int x,int y就可以了
void addtree(int &u,int l,int r,int p,int k){
    //如果该结点先前未被创建的话
    if(!u){
        //创建节点
        u=++tot;
    }
    //当递归到了叶子结点,也就是找到了下标p所在的结点
    if(l==r){
        sum[u]+=k;
        return;
    }
    //如果没递归到叶子结点
    //如果p在左半边区间,递归左半边
    if(p<=mid){
        addtree(ls[u],l,mid,p,k);
    }
    //否则递归右半边区间
    else{
        addtree(rs[u],mid+1,r,p,k);
    }
    //最后,由于递归更新了u的ls和rs,我们需要用rs和ls来更新一下u的信息
    pushup(u);
}

对于动态开点的线段树,我们一般会根据不同情况开很多棵这样子的树

最后我们需要将这样子的树合并来统计信息

也就是需要合并线段树

合并的思路很简单

假设合并以x为根和以y为根的两棵树

当我们递归到一个结点,如果x或y在这里没有结点,那么直接搬用对方的结点过来即可(如果双方在这里都没有结点,那么这里就仍然为空节点了)

然后左右子树递归

当我们递归到叶子结点的时候,合并权值

然后自下而上地pushup即可

实现代码如下:

//x,y为要合并的以x为根,以y为根的两棵树
//l,r为x结点与y结点所表示的区间
int merge(int x,int y,int l,int r){
    //如果有一方为空节点
    //直接返回另一方即可
    if(!x||!y){
        return x+y;
    }   
    //递归到了叶子结点
    if(l==r){
        sum[x]+=sum[y];
        return x;
    }
    //递归左右子树
    ls[x]=merge(ls[x],ls[y],l,mid);
    rs[x]=merge(rs[x],rs[y],mid+1,r);
    //更新x的信息
    pushup(x);
    //返回合并后的根
    return x;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值