2023.2.3对树状数组部分增加了内容,因为原文章markdown语法不太兼容所以重新发表
2023.2.6增加文章目录,对不合理的目录等级进行了修改
一般用于固定数组。可以区间求和
a = list(map(int, input().split()))
s = [0]
for i in range(n):
s.append(s[-1]+a[i])
非常高效的数据结构,可以满足大部分需求。可以区间求和、区间最大值、单点修改。一般不能区间修改(就这个结构而言)
图片出处:树状数组(求逆序对)_baby的我的博客-CSDN博客_树状数组求逆序对已征得原作者的同意使用此图。
code1(利用前缀和:会浪费空间)
for i in range(1, n):
tr[i] = d[i] - d[i-lowbit(i)] # d是前缀和数组, 因为lowbit(i)就是tr(i)维护的原数组的长度
原本nlogn就是因为有重复计算的数组,那么只要把已经建好的部分直接累加到后面就可以保证每个都只计算一次,即O(n)
code2
for i in range(1, n):
tr[i] = a[i]
j = 1
flag = lowbit(i) # 对应上面的图,每个数字下面连的就是要加上去的
while j < flag:
tr[i] += tr[i-j]
j <<= 1
code3首推(简洁)
for i in range(1, n):
tr[i] += a[i]
if (fa := i+lowbit(i)) <= n:
tr[fa] += tr[i] # +最低位变父亲,-最低位变儿子
code2\3先进的地方就在于不是利用a来推,而是利用已经建好的树,避免了a的重复叠加。code2和code3进行操作的次数一定是一样的,code2看起来多但是有些节点甚至是没子结点的
def add(x, k):
while x <= n:
tr[x] += k
x += lowbit(x)
def query(x):
ans = 0
while x:
ans += tr[x]
x -= lowbit(x)
print(query(j)-query(i-1))
def query(x, y):
"""对区间和而言是[x+1, y], 因为到x都被减掉了"""
ans = 0
while y > x:
ans += tr[y]
y -= lowbit(y)
while x > y:
ans -= tr[x]
x -= lowbit(y)
非常灵活的数据结构。可以区间求和、区间最大值、区间修改、单点修改。
def update(L,R,p,pl, pr, d) :
if L<=pl and R>=pr: # 某区间的子树都包含在这个区间里,这样取可以全部取到
add_tag (p, pl, pr,d)
return
push_down(p,pl, pr) # 将懒惰标记传递给孩子(在前面标记过且这次有不包含在里面的元素)
mid = pl + pr >> 1
if L <= mid:
update(L,R,p << 1,pl, mid, d)
if R >= mid + 1:
update(L,R, p << 1|1, mid+1, pr,d)
tree[p] = tree[p << 1] + tree[p << 1|1] # push_up(p)
因为某区间的子树都包含在这个区间里,所以不包含的区间一定都是不包含的,这样在if L<=pl and R>=pr 时就可以取到所求区间里的所有部分。
def push_down(p, pl, pr):
if tag[p]>0: # 有tag标记,这是以前做区间修改时留下的,相当于延时标记
mid = pl+pr >> 1
add_tag (p<<1,pl,mid, tag[p]) # 把tag标记传给左子树
add_tag (p<<1|1,mid+1, pr, tag[p]) # 把tag标记传给右子树tag[p]=0
#p自己的tag被传走了,归0
push_down相当于延迟标记,在要用到的时候向下传。但是push_down是单次的,要借用update的递归才能一层一层向下传递,直到能覆盖区间。到能覆盖的节点,懒标记还在,但是懒标记的值是上一次传递的(相当于值只传递到本次要用的地方,后面不再传递)。本次更新的值在后面会进行叠加
懒标记实质:只计算出确实需要访问的区间的真实值,其他的保存在lazytag里面,这样可以近似O(nlogn)的运行起来。
def add_tag (p, pl, pr, d) :
"""给结点p打tag标记,并更新tree"""
tag[p] += d # 打上tag标记。并不是等于d是因为可能有上次的懒标记传递
tree[p] += d*(pr-pl+1) # 计算新的tree