大佬用的线段树刷题的实例:https://leetcode-cn.com/submissions/detail/300962892/
[LCP 52. 二叉搜索树染色](https://timpcfan.notion.site/LCP-52-6df3aa029d8d45c2b16da8c2dd4bb834)
class Solution:
def numTeams(self, rating: List[int]) -> int:
'''线段树模板'''
def add(i, delta): # 坐标i处依次添加delta
i += len(tree)//2 # 转换到线段树下标
while i > 0:
tree[i] += delta
i //= 2
def query(i, j): # 区域和检索 range sum
i += len(tree) // 2 # 转换到线段树下标
j += len(tree) // 2
summ = 0
while i <= j:
if i % 2 == 1: # i为右子树
summ += tree[i]
i += 1
if j % 2 == 0: # i为左子树
summ += tree[j]
j -= 1
i //= 2
j //= 2
return summ
'''主程序'''
# 离散化:绝对数值转秩次rank
uniques = sorted(rating) # rating没有重复值
rank_map = {v:i for i,v in enumerate(uniques)} #【注意:rank从0开始】
# print(rank_map)
# 构建线段树
tree = [0] * (len(rank_map) * 2)
# 枚举中间点
ans = 0
n = len(rating)
add(rank_map[rating[0]], 1) # 先将第一个元素入列
for i in range(1, n-1): # 从第二个元素开始遍历,直至倒数第二个元素
rank = rank_map[rating[i]] # 当前元素的排名/秩次
small1 = query(0, rank-1) # 查询前序元素中排名<rank的元素数目
large1 = i - small1 # small1 + large1 = i
small2 = rank - small1 # small1 + small2 = rank
large2 = n-1 - i - small2 # small2 + large2 = n-1-i
add(rank, 1) # 当前元素入列
ans += small1*large2 + large1*small2
return ans
作者:flix
链接:<https://leetcode.cn/problems/count-number-of-teams/solution/by-flix-3lu9/>
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
线段树是算法竞赛中常用的用来维护 区间信息 的数据结构。
线段树可以在 $O(\log N)$ 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。
线段树维护的信息在很多时候可以认为是满足(幺)半群的性质的信息。
一个幺半群 $M=(S,\circ ,e)$,其中 $\circ$ 为在集合 $S$ 上定义的二元运算符,幺半群具有以下性质:
我们观察到线段树上的信息一般满足这样的性质,一些数域上的加法与乘法自然,考虑二元的 $\max(x,y)$ 运算,此时幺元为 $-\infty$ 也满足这样的性质(一般左右幺元相同时简称为幺元)。
线段树将每个长度不为 1 的区间划分成左右两个区间递归求解,把整个线段划分为一个树形结构,通过合并左右两区间信息来求得该区间的信息。这种数据结构可以方便的进行大部分的区间操作。
有个大小为 5 的数组 $a=\{10,11,12,13,14\}$,要将其转化为线段树,有以下做法:设线段树的根节点编号为 1,用数组 d 来保存我们的线段树,d_i 用来保存线段树上编号为 i 的节点的值(这里每个节点所维护的值就是这个节点所表示的区间总和)。