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

简介: 线段树-区间求和修改-python

题目:


给定一个长度为 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

输入


1. 5 5
2. 1 2 3 4
3. 2 1 2
4. 1 2 3 1
5. 2 1 3
6. 1 1 5 1
7. 2 1 5

输出

1. 3
2. 8
3. 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 个结点。


代码


1. N=100001
2. [n,m]=list(map(int,input().split()))
3. num=list(map(int,input().split()))
4. class SegTree():
5.     def __init__(self):
6. self.sum=0
7. self.tag=0
8. tr=[SegTree() for i in range(N*4)]
9. #通过递归,先到叶子节点,再从下往上建树
10. def build(p,l,r):
11. if(l==r):
12.         #最底层的叶子,赋值
13.         tr[p].sum=num[l-1]
14. return
15. global segs
16.     mid=(l+r)>>1
17.     build(p<<1,l,mid)
18.     build(p<<1|1,mid+1,r)
19.     tr[p].sum=tr[p<<1].sum+tr[p<<1|1].sum
20. return
21. 
22. #把tag传给子树,并且更新子树的值
23. #用来解决tag冲突问题
24. def down(p,s):
25.     #将tag传给左子树
26.     tr[p<<1].sum+=tr[p].tag*(s-(s>>1))
27.     tr[p<<1].tag+=tr[p].tag
28.     #将tag传给右子树
29.     tr[p<<1|1].sum+=tr[p].tag*(s>>1)
30.     tr[p<<1|1].tag+=tr[p].tag
31.     tr[p].tag=0
32. return
33. 
34. #区间修改,把x,y之内的元素加上z
35. def modify(x,y,z,p,l,r):
36.     #如果l,r在【x,y】中
37. if(x<=l)&(r<=y):
38.         #给结点p打标记,并且更新结点的值
39.         tr[p].sum+=z*(r-l+1)
40.         tr[p].tag+=z
41.         #如果完全覆盖,直接返回这个结点
42.         #它的子树不用再深入
43. return
44.     #如果不在,且tr之前被打过标记
45.     #把tag传给子树
46. if(tr[p].tag!=0):
47. down(p,r-l+1)
48.     mid=(l+r)>>1
49.     #递归左子树
50. if(x<=mid):
51.         modify(x,y,z,p<<1,l,mid)
52.     #递归右子树
53. if(y>mid):
54.         modify(x,y,z,p<<1|1,mid+1,r)
55.     #更新树上的结点
56.     tr[p].sum=tr[p<<1].sum+tr[p<<1|1].sum
57. return
58. 
59. #查询区间的和
60. def query(x,y,p,l,r):
61.     #完全覆盖,直接返回
62.     #[l,r]在[x,y]树结构的上面,此时p就是区间和
63. if(x<=l)&(r<=y):
64. return tr[p].sum
65.     #不能覆盖,递归子树
66. if(tr[p].tag!=0):
67. down(p,r-l+1)
68.     mid=(l+r)>>1
69.     res=0
70.     #左节点有重合
71. if(x<=mid):
72.         res+=query(x,y,p<<1,l,mid)
73.     #右节点有重合
74. if(y>mid):
75.         res+=query(x,y,p<<1|1,mid+1,r)
76. return res
77. build(1,1,n)
78. while(m>0):
79.     m-=1
80.     op=list(map(int,input().split()))
81. if(op[0]==1):
82.         modify(op[1],op[2],op[3],1,1,n)
83. else:
84.         print(query(op[1],op[2],1,1,n))
目录
相关文章
|
5月前
|
存储 SQL 算法
高效日程管理:利用区间合并算法优化活动安排【python LeetCode57】
高效日程管理:利用区间合并算法优化活动安排【python LeetCode57】
|
5月前
|
存储 算法 搜索推荐
掌握区间合并:解决实际问题的算法策略和应用案例【python LeetCode题目56】
掌握区间合并:解决实际问题的算法策略和应用案例【python LeetCode题目56】
|
Python
使用Python实现商品价格区间设置和排序
使用Python实现商品价格区间设置和排序
378 0
|
6月前
|
算法 C++ 机器人
力扣 C++|一题多解之动态规划专题(1)
力扣 C++|一题多解之动态规划专题(1)
63 0
力扣 C++|一题多解之动态规划专题(1)
|
6月前
|
C++ 存储
力扣C++|一题多解之数学题专场(1)
力扣C++|一题多解之数学题专场(1)
51 0
力扣C++|一题多解之数学题专场(1)
|
6月前
|
Python Go Java
Golang每日一练(leetDay0044) 被围绕的区域、分割回文串I\II
Golang每日一练(leetDay0044) 被围绕的区域、分割回文串I\II
49 0
Golang每日一练(leetDay0044) 被围绕的区域、分割回文串I\II
|
6月前
|
Python C++ Java
C/C++每日一练(20230423) 多组输入求和、螺旋矩阵II、路径交叉
C/C++每日一练(20230423) 多组输入求和、螺旋矩阵II、路径交叉
34 0
C/C++每日一练(20230423) 多组输入求和、螺旋矩阵II、路径交叉
|
6月前
|
定位技术 索引 Python
Python自动计算Excel数据指定范围内的区间最大值
Python自动计算Excel数据指定范围内的区间最大值
|
存储 算法 Python
蓝桥杯-区间最大值-python
蓝桥杯-区间最大值-python
150 0
|
Python
Python漫游数学王国 | 总体参数的区间估计
本文讨论总体参数的区间估计。
180 0