首页 > 其他分享 >浅谈线段树

浅谈线段树

时间:2022-10-18 22:58:05浏览次数:73  
标签:浅谈 val int 线段 mid seg 节点

浅谈线段树Segment_Tree

By xiaruize

引言

OI中,有一种好玩的游戏,叫做码线段树,那么线段树是什么???

线段树的目的

线段树主要用于在区间上动态维护一些值(如最大值,最小值,和,积等)

线段树的实现

以区间最大值为例,给定一个长度为 \(n\) 的数组,每次查询求 \([l,r]\) 之间的最大值,或将 \([l,r]\) 赋值为 \(val\)

显然,如果暴力去维护这个问题,需要 \(O(NQ)\) 的时间复杂度, 并不能解决\(1 \leq n,q \leq 10^5\)级别的问题

此时,我们考虑将原问题分解为 (\([l,mid]\) 的 \(max\) 与 \([mid+1,r]\) 的 \(max\)) 的 \(max\)

接着,我们可以以此类推,细分到只有一个节点

当 \(n=6\) 时, 线段树如下图

{% asset_img seg1.png n=6时的线段树形态 %}

线段树的存储

一般线段树使用和二叉树类似的方式存储 注意4倍空间

这样存储的好处在于,对于点\(p\), 它的左儿子是$ p<<1 $, 右儿子是 \(p<<1|1\), 父节点是 \(p>>1\)

建议使用define定义ls,rs为左右儿子

上传pushup

该操作用于通过\(p\)节点的儿子更新\(p\)节点

以求最大值为例

void pushup(int p)
{
    seg[p].val=max(seg[ls].val,seg[rs].val);
}

建树 build

首先,肯定需要建树,递归即可

void build(int l,int r,int p)
{
    seg[p].l=l;
    seg[p].r=r; //可以放在函数的参数里
    if(l==r) //到叶子节点
    {
        seg[p].val=a[l]; //赋值
        return;
    }
    int mid=(l+r)>>1; //不打括号也可以,+的优先级比>>高
    build(l,mid,ls); //处理左儿子
    build(mid+1,r,rs); //处理右儿子
    pushup(p); //更新当前节点
}

也可以将当前的区间放在参数里,主要看个人习惯

修改update

单点修改

如果只需要修改一个点,那么可以通过 \(l,r\) 的指引, \(dfs\) 到这个点,修改它的值,返回时进行一遍 \(pushup\)

void update(int p,int d,int val)
{
    if(seg[p].l==seg[p].r) //到达需要修改的节点
    {
        seg[p].val=d;
        return;
    }
    int mid=seg[p].l+seg[p].r>>1;
    if(d<=mid)//在左侧
        update(ls,d,val);
    if(d>mid)//在右侧
        update(rs,d,val);
    pushup(p); //更新当前节点
}

区间修改(lazy tag)

我们在回头看一下题目,发现需要支持区间修改,如果做\(n\)次单点修改,会消耗大量的时间

此时,我们可以在一段在子节点都需要修改的节点上打一个懒标记(lazy tag),表示当前节点及下属的节点都需要修改

例如,修改区间[1,5]为2

这样,可以用更优的速度完成修改操作

void update(int u, int l, int r, int d)
{
    if (tr[p].l >= l && tr[p].r <= r)  //更新节点并打懒标记
    {
        tr[p].val=max(tr[u].val,d);
        tr[p].laz=max(tr[u].laz,d);
    }
    else
    {
        pushdown(p); //见下方
        int mid = tr[p].l + tr[p].r >> 1;
        if (l <= mid) update(ls, l, r, d); //左侧存在需要修改的节点
        if (r > mid) update(rs, l, r, d); //右侧存在需要修改的节点
        pushup(p); //更新当前节点
    }
}

下传懒标记pushdown

如果你认真阅读了上面的代码,会发现pushdown函数并没有讲过

那么pushdown的作用其实是下传懒标记

及将父亲节点记录的修改(laz)传给儿子

void pushdown(int p)
{
    seg[ls].laz=seg[p].laz;
    seg[ls].val=seg[p].laz;
    seg[rs].laz=seg[p].laz;
    seg[rs].val=seg[p].laz;
    seg[p].laz=0;
}

查询 query

那么,已经有了一棵线段树,要如何查询区间最值呢?

可以去模仿update操作,每次询问左区间和右区间,在合并答案

int query(int l,int r,int ll,int rr,int p) //把当前区间放在了参数里的写法
{
    if(l>=ll&&r<=rr)
    {
        return seg[p].val; //到达节点,返回答案
    }
    int mid=(l+r)>>1;
    int res=0;
    pushdown(p);//下传懒标记
    if(mid<rr)
        res=max(res,query(mid+1,r,ll,rr,rs)); //通过左区间更新
    if(mid>=ll)
        res=max(res,query(l,mid,ll,rr,ls)); //通过右区间更新
    return res;
}

读到这里,你应该已经可以基本掌握如何码线段树. 但是,要在码线段树的游戏中超过你的同伴,你需要多加练习

标签:浅谈,val,int,线段,mid,seg,节点
From: https://www.cnblogs.com/xiaruize/p/16804503.html

相关文章

  • 浅谈C语言中的变量
    一.定义变量的方法就是类型+变量名+数值,比如:inta=12;shortage=22;charch='w';二.变量的分类1.全局变量2.局部变量全局变量:定义在代码块({})之外的变量。局部......
  • 浅谈C语言中的常量(字面常量、const修饰的常变量、#define定义的标识符常量、枚举常量
    一.常量不会变的量就是常量,比如性别,血型等;二.常量的分类1.字面常量2.const修饰的常变量3.#define定义的标识符常量4.枚举常量1.字面常量2.const修饰的常变量    在......
  • 线段树 __ 复习
    线段树的结构为什么叫线段树?因为它是把原序列以及其子序列(一个个线段)组织成一棵树的形式。树的根节点为原序列,子节点依次对半分序列,直到叶节点,叶节点是单个数,也没办法再......
  • 线段树应用模板题
    利用刚学的线段树写一道模板题https://www.acwing.com/problem/content/1272/这里不需要更新修改,但是要求的却是最大值因而pushup时,也是取max,build与模板一样查询的que......
  • P6492 [COCI2010-2011#6] STEP(线段树维护左右区间pushup)
    题目链接题目大意:\(~~\)初始给定一个长度为n的字符序列a,初始序列中全是\(~\)L\(~~\)共有q次修改,修改a\(_{x}\)为:L\(\rightarrow\)\(~~\)or\(~~\)R\(\rightarrow\)L\(......
  • 浅谈区块链应用中的密码学原理
    你印象中的区块链是什么?比特币、以太坊等加密货币?这样的理解是片面的,可以这么说比特币是区块链最成功的应用。当然基于区块链的去中心化共识的应用层出不穷,下面将举几个应......
  • 做题记录整理数据结构/线段树3 P3822 [NOI2017] 整数(2022/10/17)
    P3822[NOI2017]整数为什么这玩意是双tag呢因为他按理来说正解是用线段树来做的,但是有很多题解都是直接上set搞的,所以就两个tag都打上了首先我们可以想到用bitset来表......
  • 线段树维护哈希
    显然两个区间的哈希值是可以合并的,所以线段树可以维护区间的哈希值。设左儿子的长度和哈希值分别为\(sz_a,h_a\),右儿子的长度和哈希值分别为\(sz_b,h_b\),合并后的长度为......
  • CF240F (26颗线段树计数)
    题目链接:Topcoder----洛谷题目大意:给定一个长为n的由a到z组成的字符串,有m次操作,每次操作将[l,r]这些位置的字符进行重排,得到字典序最小的回文字符串,如果无法操作就......
  • 浅谈Splay
    splay:伸展树,通过一系列的旋转维护平衡性。注意,splay不一定是严格的平衡。故操作大部分均摊时间复杂度\(O(logn)\)分3种情况讨论旋转:\(1.\)Zig\(or\)Zag\(2.\)Zi......