线段树

本文详细介绍了线段树数据结构,主要用于区间修改和查询,具有O(log n)的时间复杂度。内容包括线段树的定义、建树过程、单点修改、区间修改及查询操作,并给出了HDU1166和POJ 3468两个例题进行实战解析。
摘要由CSDN通过智能技术生成
本博客同步更新至 https://startcraft.cn

介绍

线段树的应用场景在区间修改和区间查询上,修改和查询的时间复杂度都为O(log n)
线段树如它的名字一样将区间分成一段一段的,如123456789这一串数字,以区间求和为例

上图就是构建的线段树,其中结点里的值是该结点表示的区间的和,中括号里的数字代表表示的区间

实现(以区间求和为例)

定义

线段树的根结点定义为1,那么左子节点就是n*2,右子节点是n*+1
通常2*n在代码中写成 n<<1 。 2*n+1写成 n<<1|1 。
线段树所需的空间
足够的空间 = 数组大小n的四倍。
实际上足够的空间 = (n向上扩充到最近的2的某个次方)的两倍。

const int maxn = 100005;//元素的个数
ll tree[maxn << 2];//线段树的结点,大小开元素的4倍
ll add[maxn << 2];//标记数组(懒惰标记)区间修改会用到

建树

void push_up(int id)//根据子节点来更新父节点
{
   
    tree[id] = tree[id << 1] + tree[id << 1 | 1];
}
void build(int l, int r, int id)//l,r表示当前区间,id表示当前结点的下标
{
   
    if (l == r)//到叶子结点就赋值
    {
   
        tree[id] = num[l];
        return ;
    }
    int mid = (l + r) >> 1;
    build(l, mid, id << 1);//递归构建左子树
    build(mid + 1, r, id << 1 | 1);//递归构建右子树
    push_up(id);//当前结点的子节点已经构建完成,然后更新当前结点
}

单点修改

void update(int l, int r, int now, int number, int pos)
//l,r是当前区间,now是当前结点下标,number是要修改的数,pos是要修改的下标
{
   
    if (l == r)//到叶子结点直接修改
    {
   
        tree[now] += number;
        return ;
    }
    int mid = (l + r) >> 1;
    if (pos <= mid)//判断要修改的点在左子树还是右子树,然后递归修改
        update(l, mid, now << 1, number, pos);
    else
        update(mid + 1, r, now << 1 | 1, number, pos);
    push_up(now);//当前结点的子节点已经更新,所以更新当前结点
}

区间修改

关于区间的修改需要用到标记数组,改数组的含义是"当前结点的值已经更新,但是子节点的值未更新"
如add[7]=3,代表结点7的值已经更新,但是结点7的子节点都还没有加上这个值,这就需要一个下推标记的函数

void push_down(int id, int ln, int rn)
//id代表当前结点,ln代表左区间的元素个数,rn代表右区间的元素个数
{
   
    if (add[id] != 0)
    {
   
        tree[id << 1] += add[id] * ln;//更新左区间的值
        tree[id << 1 | 1] += add[id] * rn;//更新右区间的值
        add[id << 1] += add[id];//将标记下推,更新左区间标记
        add[id << 1 | 1] += add[id];//更新右区间标记
        add[id] = 0;//清空当前结点标记
    }
}

修改

void update(int l, int r, int L
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值