XCPC第十二讲,带你学会线段树

在这里插入图片描述

        今天我们来学习线段树这一数据结构。线段树是一种二叉树,它的每一个节点存储一段区间的信息(如该区间内所有元素的和)。以终点为分界,每个节点衍生出两个子节点,直到某个节点表示的区间只有一个数。如图——
在这里插入图片描述

线段树(第一版)

在这里插入图片描述
        我们可以利用线段树来实现题目的需求。现在我们假设数列中有五个数,那么线段树如下——
在这里插入图片描述

第一种操作

        线段树中的每个端点存储的是这个端点所代表的区间内的元素的和。假若我们现在要给第三个数字加上y,那么我们可以发现,从第三个数字所在的最小区间(即只有它自己的区间)到根节点所表示的区间路径上的点表示的区间都要加y,这是因为它们都包含第三个数。

第二种操作

        要求得左端点为x右端点为y的区间内的元素和,我们的基本想法是把这个大区间分成一个个小区间,分别对每个小区间求和再加起来。在这个过程中,如果我们把某个节点的两个子节点都选上了,那么就可以直接选该节点而不选子节点,从而减少计算量。譬如我们要求【3,5】这个区间的元素和,我们把它分成【3,3】,【4,4】和【5,5】三个小区间。由于【4,5】的两个子区间都选了,我们就可以直接选【4,5】来代替它们两个。这样一来,由于区间是连续的,每层最多只可能选两个靠边的区间。如图——
在这里插入图片描述
        要求绿颜色范围内的区间,最后一层最多只会选两个区间。下面让我们来看看两种操作具体的代码实现吧!

#include<bits/stdc++.h>
using namespace std;
//开数列元素的四倍大小来存储节点,这里不加证明
const int N = 2000010,M = 500010;
//f[i]表示第i个节点表示的区间(节点与区间时一一对应的)的元素和,root表示读入的原数列
int n,m,f[N],root[N];
inline void buildtree(int k,int l,int r)
{
   
    if(l==r)
    {
   
        //如果l==r,说明递归到叶子节点(即一个区间只有一个值),那么此时的f[k](即下标为k的区间内元素的和)就是root[l]本身
        f[k] = root[l];
        //这里不要忘记return!
        return;
    }
    //取出区间的中点,递归地建立左子树和右子树
    int m = (l+r)>>1;
    buildtree(k+k,l,m);
    buildtree(k+k+1,m+1,r);
    //别忘了这一句话,要把左右两个子节点代表的区间的元素和加起来赋给父节点
    f[k] = f[k+k]+f[k+k+1];
}
//这句话意思是在下标k表示的左端点为l右端点为r的区间中的编号为x的数加上y
inline void add(int k,int l,int r,int x,int y)
{
   
    //编号为k的区间内的点x加上了y,那么该区间和自然也加上y,因为该区间含有x
    f[k]+=y;
    //如果l==r,说明已经递归到叶子节点,说明该路径上含有x的区间的和都加上了y,返回即可
    if(l==r)    return;
    int m = (l+r)>>1;
    //如果x落在左子树内,就递归地让左子树上含有x的区间和都加上y。x在右子树类似
    if(x<=m)    add(k+k,l,m,x,y);
    else add(k+k+1,m+1,r,x,y);
}
//这句话的意思是,在节点k代表的区间【l,r】内求区间【s,t】的元素和
inline int calculate(int k,int l,int r,int s,int t)
{
   
    //如果l==s&&r==t,那么要查询的区间就是当前区间,直接返回节点k代表的区间的元素和即可
    if(l==s&&r==t)    return f[k];
    int m = (l+r)>>1;
    //t<=m说明要查询的区间完全落在左半边
    if(t<=m)    return calculate(k+k,l,m,s,t);
    //s>m说明要查询的区间完全落在右半边
    else if(s>m)  return calculate(k+k+1,m+1,r,s,t);
    //否则要查询的区间横跨左右两边,只要分别查询并求和即可,相当于剖成两个区间了
    else    return calculate(k+k,l,m
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值