区间修改与区间求和是算法竞赛中最经典的数据结构问题之一,也是蓝桥杯等编程赛事的高频考点。题目1133要求我们实现一个数据结构,能够高效处理以下两种操作:
这类问题在真实业务场景中非常常见,比如:
最直观的解法是直接遍历区间进行修改和求和:
python复制# 区间修改
for i in range(l, r+1):
arr[i] += value
# 区间求和
total = 0
for i in range(l, r+1):
total += arr[i]
时间复杂度:修改O(n),查询O(n)
当n达到1e5量级时,这种解法显然无法满足要求。
线段树是解决此类问题的标准数据结构,它将区间信息存储在二叉树结构中,每个节点代表一个区间。
python复制class SegmentTreeNode:
def __init__(self, l, r):
self.l = l # 区间左边界
self.r = r # 区间右边界
self.left = None # 左子节点
self.right = None # 右子节点
self.sum = 0 # 区间和
self.lazy = 0 # 延迟标记
python复制def build(l, r, arr):
node = SegmentTreeNode(l, r)
if l == r:
node.sum = arr[l]
return node
mid = (l + r) // 2
node.left = build(l, mid, arr)
node.right = build(mid+1, r, arr)
node.sum = node.left.sum + node.right.sum
return node
建树时间复杂度:O(n)
线段树的核心优化在于延迟标记(lazy tag):
python复制def push_down(node):
if node.lazy != 0:
if node.left:
node.left.sum += node.lazy * (node.left.r - node.left.l + 1)
node.left.lazy += node.lazy
if node.right:
node.right.sum += node.lazy * (node.right.r - node.right.l + 1)
node.right.lazy += node.lazy
node.lazy = 0
def range_update(node, l, r, val):
if node.r < l or node.l > r:
return
if l <= node.l and node.r <= r:
node.sum += val * (node.r - node.l + 1)
node.lazy += val
return
push_down(node)
range_update(node.left, l, r, val)
range_update(node.right, l, r, val)
node.sum = node.left.sum + node.right.sum
修改时间复杂度:O(logn)
python复制def range_query(node, l, r):
if node.r < l or node.l > r:
return 0
if l <= node.l and node.r <= r:
return node.sum
push_down(node)
return range_query(node.left, l, r) + range_query(node.right, l, r)
查询时间复杂度:O(logn)
对于数组a,定义差分数组d:
区间[l,r]加val等价于:
python复制class FenwickTree:
def __init__(self, size):
self.n = size
self.tree = [0] * (self.n + 2)
def update(self, idx, delta):
while idx <= self.n:
self.tree[idx] += delta
idx += idx & -idx
def query(self, idx):
res = 0
while idx > 0:
res += self.tree[idx]
idx -= idx & -idx
return res
# 区间修改
def range_add(ft1, ft2, l, r, val):
ft1.update(l, val)
ft1.update(r+1, -val)
ft2.update(l, val * (l-1))
ft2.update(r+1, -val * r)
# 区间查询
def range_sum(ft1, ft2, l, r):
def prefix_sum(idx):
return ft1.query(idx) * idx - ft2.query(idx)
return prefix_sum(r) - prefix_sum(l-1)
该方案同样实现O(logn)的修改和查询。
| 特性 | 线段树 | 树状数组 |
|---|---|---|
| 时间复杂度 | O(logn) 修改/查询 | O(logn) 修改/查询 |
| 空间复杂度 | O(4n) | O(2n) |
| 代码复杂度 | 较高 | 较低 |
| 扩展性 | 支持更多区间操作 | 主要适合求和 |
| 调试难度 | 较高 | 较低 |
实际比赛建议:如果只需要区间加减和求和,优先选择树状数组;如果需要处理更复杂的区间操作(如最值、GCD等),则必须使用线段树。
区间边界错误:
延迟标记未正确下传:
数组大小不足:
差分数组初始化:
python复制# 正确初始化方式
ft1 = FenwickTree(n)
ft2 = FenwickTree(n)
for i in range(1, n+1):
range_add(ft1, ft2, i, i, arr[i-1])
1-based索引:
边界检查:
非递归实现:
动态开点:
标记永久化:
多维树状数组:
python复制class FenwickTree2D:
def __init__(self, rows, cols):
self.rows = rows
self.cols = cols
self.tree = [[0]*(cols+1) for _ in range(rows+1)]
离线处理:
实现一个支持以下操作的系统:
解决方案:
python复制# 使用离散化+树状数组
def solve():
import bisect
n = int(input())
scores = list(map(int, input().split()))
# 离散化
sorted_scores = sorted(set(scores))
mapping = {v:i+1 for i,v in enumerate(sorted_scores)}
ft = FenwickTree(len(sorted_scores))
for s in scores:
ft.update(mapping[s], 1)
# 查询[l,r]区间人数
l_pos = bisect.bisect_left(sorted_scores, l)
r_pos = bisect.bisect_right(sorted_scores, r)
return ft.query(r_pos) - ft.query(l_pos)
在RPG游戏中,需要实时计算:
解决方案:
python复制# 二维线段树实现
class SegmentTree2D:
def __init__(self, matrix):
self.matrix = matrix
self.rows = len(matrix)
if self.rows == 0: return
self.cols = len(matrix[0])
self.root = self.build(0, self.rows-1, 0, self.cols-1)
def build(self, row1, row2, col1, col2):
# 实现类似一维线段树的构建逻辑
pass
def range_update(self, row1, row2, col1, col2, delta):
# 实现二维区间更新
pass
def range_query(self, row1, row2, col1, col2):
# 实现二维区间查询
pass
python复制class SegmentTree:
def __init__(self, data):
self.n = len(data)
self.size = 1
while self.size < self.n:
self.size <<= 1
self.tree = [0] * (2 * self.size)
self.lazy = [0] * (2 * self.size)
# 初始化叶子节点
for i in range(self.n):
self.tree[self.size + i] = data[i]
# 构建内部节点
for i in range(self.size - 1, 0, -1):
self.tree[i] = self.tree[2*i] + self.tree[2*i+1]
def push(self, node, node_l, node_r):
if self.lazy[node] != 0:
mid = (node_l + node_r) // 2
# 更新左子节点
self.tree[2*node] += self.lazy[node] * (mid - node_l + 1)
self.lazy[2*node] += self.lazy[node]
# 更新右子节点
self.tree[2*node+1] += self.lazy[node] * (node_r - mid)
self.lazy[2*node+1] += self.lazy[node]
# 清除当前节点标记
self.lazy[node] = 0
def range_add(self, l, r, val, node=1, node_l=0, node_r=None):
if node_r is None:
node_r = self.size - 1
if r < node_l or l > node_r:
return
if l <= node_l and node_r <= r:
self.tree[node] += val * (node_r - node_l + 1)
self.lazy[node] += val
return
self.push(node, node_l, node_r)
mid = (node_l + node_r) // 2
self.range_add(l, r, val, 2*node, node_l, mid)
self.range_add(l, r, val, 2*node+1, mid+1, node_r)
self.tree[node] = self.tree[2*node] + self.tree[2*node+1]
def range_query(self, l, r, node=1, node_l=0, node_r=None):
if node_r is None:
node_r = self.size - 1
if r < node_l or l > node_r:
return 0
if l <= node_l and node_r <= r:
return self.tree[node]
self.push(node, node_l, node_r)
mid = (node_l + node_r) // 2
return self.range_query(l, r, 2*node, node_l, mid) + \
self.range_query(l, r, 2*node+1, mid+1, node_r)
比赛时的快速实现技巧:
调试方法:
进阶学习方向: