线段树-区间求和修改-python

目录

题目:

输入描述

输出描述

输入输出样例

运行限制

思路

区间和查询

 区间修改和懒惰标记

我们下面介绍4个函数:

注意树的存储空间

代码


题目:

给定一个长度为 N 的数组 a,其初值分别为a1​,a2​,...,aN​。

现有 Q 个操作,操作有以下两种:

  • 1 l r k,将区间 al​,al+1​,...ar​ 的值加上 k。
  • 2 l r,求区间al​,al+1​,...,ar​ 的和为多少。

输入描述

输入第 1 行包含两个正整数 N,Q,分别表示数组 a 的长度和操作的个数。

第 2 行包含 N 个非负整数 a1​,a2​,...,aN​,表示数组 a 元素的初值。

第 3∼Q−2 行每行表示一共操作,格式为以下两种之一:

  • 1 l r x
  • 2 l r

其含义如题所述。

1≤N,Q≤10^5,1≤l≤r≤N,−10^9≤k,ai​≤10^9。

输出描述

输出共 Q 行,每行包含一个整数,表示相应查询的答案。

输入输出样例

示例 1

输入

5 5
1 2 3 4 
2 1 2
1 2 3 1
2 1 3
1 1 5 1
2 1 5

输出

3
8
22

运行限制

  • 最大运行时间:2s
  • 最大运行内存: 256M

思路

区间和查询

我们可以通过线段树的数据结构查询一个区间的最大值,如上图所示,是一棵表示区间和的线段树,它用于查询{1, 4, 5, 8, 6, 2, 3, 9, 10, 71,4,5,8,6,2,3,9,10,7​​} 区间和,每个结点上圆圈内的数字是这棵子树的和。例如查区间 [4, 9][4,9] 的和,递归查询到区间 [4, 5]、[6, 8]、[9, 9][4,5]、[6,8]、[9,9],见图中画横线的线段,得 sum{14, 14, 10} = 38。

查询递归到某个结点 p​(p表示的区间是 [pl, pr])时,有 2 种情况:

1.[L,R] 完全覆盖了 [pl, pr] ,即 L ≤ pl ≤ pr ≤ R,说明[l,r]在[x,y]树结构的上面,此时p就是区间和直接返回 p 的值即可。

2.[L,R] 与 [pl, pr] 部分重叠,分别搜左右子结点。

  • L < pr,继续递归左子结点,例如查询区间 [4,9],与第 2 个结点 [1,5] 有重叠,因为 4 < 5,进入递归把[4,5]的区间和返回。
  • R > pl​,继续递归右子结点,例如 [4, 9]​ 与第 3​ 个结点 [6,10]​ 有重叠,因为9>6。进入递归把[6,8],[9,9]的区间和返回

区间和的查询将主要体现在代码中的query()函数中。 

 区间修改和懒惰标记

我们可以通过在每一个结点中定义一个tag[i]来统一记录这个区间的修改,而不必一个个的修改区间内每个元素。这个方法就是”lazy-tag“。

当修改的是一个线段区间时,就只对这个线段区间进行整体上的修改,其内部每个元素的内容先不做修改,只有当这个线段区间的一致性被破坏时,才把变化值传递给下一层的子区间。这样每次区间修改的复杂度是O(logn)的,一共 m 次操作,总复杂度是 O(mlogn)的(区间 i的 lazy操作,我们用 tag[i]记录)。

比如下图:

[4,9] 区间内的每个元素加 3,执行步骤是:

  1. 左子树递归到结点 5,即区间 [4,5],完全包含在 [4,9] 内,打标记tag[5]=3,更新 tree[5] 为 20,不再继续深入;
  2. 左子树递归返回,更新 tree[2]为 30;
  3. 右子树递归到结点 6​,即区间 [6,8]​,完全包含在 [4, 9][4,9] 内,打标记tag[6]=3,更新 tree[6] 为 23。
  4. 右子树递归到结点 14,即区间 [9, 9],打标记 tag[14]=3,更新tree[14]=13;
  5. 右子树递归返回,更新 tree[7]=20;继续返回,更新 tree[3]=43;
  6. 返回到根结点,更新 tree[1]=73。

但我们要注意到一点, 如果发生了多次修改,即同一个结点上的 tagtag 发生多次修改,会导致冲突吧。例如做 2 次区间修改,一次是 [4,9],一次是[5,8],它们都会影响 5:[4,5] 这个结点。第一次修改 [4,9] 覆盖了结点 5,用 tag[5] 做了记录;而第二次修改 [5,8] 不能覆盖结点 5,需要再向下搜到结点 11:[5,5] ,从而破坏了 tag[5],此时原 tag[5] 记录的区间统一修改就不得不往它的子结点传递和执行了,传递后 tag[5] 失去了用途,应当清空为0。为此我们设计down()函数,用于处理tag冲突问题。

我们下面介绍4个函数:

1.build()

建树,通过递归,先到叶子节点,再从下往上建树

2.down()

把tag传给子树,给左右子树的tag[]赋值,并且更新子树结点的值,用来解决tag冲突问题。

3.modify()

  • 如果 tree[p]tree[p] 这棵子树完全被包含在需要修改的区间 [L, R][L,R] 中,那么只需要对根 tree[p]tree[p] 打上标记即可,不用修改子结点。
  • 如果不能覆盖,需要解决多次修改的冲突问题,用 push_down() 实现。

然后后面就是二分了。二分之后再把修改值传回上层。

4.query()

查询区间和,见上文

注意树的存储空间

树的存储空间要4N,因为假设有一棵处理 n 个元素(叶子结点有 n 个)的线段树,且它的最后一层只有 1 个叶子,其他层都是满的;如果用满二叉树表示,它的结点总数是:最后一层有 2n 个结点(其中 2n−1 个都浪费了没用到),而前面所有的层有 2n 个结点,加起来共 4n 个结点。

代码

N=100001
[n,m]=list(map(int,input().split()))
num=list(map(int,input().split()))
class SegTree():
    def __init__(self):
        self.sum=0
        self.tag=0
tr=[SegTree() for i in range(N*4)]
#通过递归,先到叶子节点,再从下往上建树
def build(p,l,r):
    if(l==r):
        #最底层的叶子,赋值
        tr[p].sum=num[l-1]
        return
    global segs
    mid=(l+r)>>1
    build(p<<1,l,mid)
    build(p<<1|1,mid+1,r)
    tr[p].sum=tr[p<<1].sum+tr[p<<1|1].sum
    return

#把tag传给子树,并且更新子树的值
#用来解决tag冲突问题
def down(p,s):
    #将tag传给左子树
    tr[p<<1].sum+=tr[p].tag*(s-(s>>1))
    tr[p<<1].tag+=tr[p].tag
    #将tag传给右子树
    tr[p<<1|1].sum+=tr[p].tag*(s>>1)
    tr[p<<1|1].tag+=tr[p].tag
    tr[p].tag=0
    return

#区间修改,把x,y之内的元素加上z
def modify(x,y,z,p,l,r):
    #如果l,r在【x,y】中
    if(x<=l)&(r<=y):
        #给结点p打标记,并且更新结点的值
        tr[p].sum+=z*(r-l+1)
        tr[p].tag+=z
        #如果完全覆盖,直接返回这个结点
        #它的子树不用再深入
        return
    #如果不在,且tr之前被打过标记
    #把tag传给子树
    if(tr[p].tag!=0):
        down(p,r-l+1)
    mid=(l+r)>>1
    #递归左子树
    if(x<=mid):
        modify(x,y,z,p<<1,l,mid)
    #递归右子树
    if(y>mid):
        modify(x,y,z,p<<1|1,mid+1,r)
    #更新树上的结点
    tr[p].sum=tr[p<<1].sum+tr[p<<1|1].sum
    return

#查询区间的和
def query(x,y,p,l,r):
    #完全覆盖,直接返回
    #[l,r]在[x,y]树结构的上面,此时p就是区间和
    if(x<=l)&(r<=y):
        return tr[p].sum
    #不能覆盖,递归子树
    if(tr[p].tag!=0):
        down(p,r-l+1)
    mid=(l+r)>>1
    res=0
    #左节点有重合
    if(x<=mid):
        res+=query(x,y,p<<1,l,mid)
    #右节点有重合
    if(y>mid):
        res+=query(x,y,p<<1|1,mid+1,r)
    return res
build(1,1,n)
while(m>0):
    m-=1
    op=list(map(int,input().split()))
    if(op[0]==1):
        modify(op[1],op[2],op[3],1,1,n)
    else:
        print(query(op[1],op[2],1,1,n))
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晓宜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值