树状数组(BIT)进阶:差分优化实现区间修改、区间查询

一、前言:树状数组的进阶之路

树状数组(BIT)的核心本质是以树形结构高效维护数组前缀和 ,原生仅支持两种基础操作:单点修改前缀和查询。依托这一核心特性,结合差分思想,我们已经解锁了两种经典用法:

  1. 基础版:维护原数组,实现「单点修改、区间查询」(洛谷P3374);

  2. 进阶1版:维护差分数组,实现「区间修改、单点查询」(洛谷P3368)。

但在算法竞赛中,更通用、更高频的需求是:同时支持区间修改、区间查询 。普通单树状数组无法满足该需求,因此我们基于「差分降维思想」进一步推导,使用两个树状数组配合差分,实现树状数组的终极进阶用法。

核心思路不变:利用差分将复杂的区间修改,降维转化为树状数组擅长的单点修改,再通过数学推导弥补区间查询的能力缺失。

二、前置知识回顾

1. 差分核心定义

设原数组为 a[]a[]a[],差分数组为 d[]d[]d[],满足:d[i]=a[i]−a[i−1]d[i] = a[i] - a[i-1]d[i]=a[i]−a[i−1](规定 a[0]=0a[0]=0a[0]=0)。

两大核心性质:

  1. 区间修改:对 [l,r][l,r][l,r] 全体加 kkk,等价于两次单点修改:d[l]+=k、d[r+1]−=kd[l]+=k、d[r+1]-=kd[l]+=k、d[r+1]−=k;

  2. 单点查询:原数组单点值 a[x]=∑i=1xd[i]a[x] = \sum_{i=1}^x d[i]a[x]=∑i=1xd[i](差分数组前缀和)。

2. 旧版局限

单树状数组维护差分数组,只能求出原数组单点值,无法直接求出原数组的区间和。想要实现区间查询,必须重新推导数学公式,引入第二个树状数组。

三、核心数学推导:双树状数组的由来

我们的目标:求出原数组 a[1∼x]a[1\sim x]a[1∼x] 的前缀和 S(x)=∑i=1xa[i]S(x) = \sum_{i=1}^x a[i]S(x)=∑i=1xa[i]。

由差分性质可知 a[i]=∑j=1id[j]a[i] = \sum_{j=1}^i d[j]a[i]=∑j=1id[j],将其代入前缀和公式,得到双重求和式

S(x)=∑i=1x∑j=1id[j]S(x) = \sum_{i=1}^x \sum_{j=1}^i d[j]S(x)=∑i=1x∑j=1id[j]

通过交换求和顺序 化简(核心步骤):原本是先枚举原数组下标,再枚举差分前缀;交换后统计每个 d[j]d[j]d[j] 被累加的次数。

对于 d[j]d[j]d[j],在 1∼x1\sim x1∼x 的范围内,会被累加 x−j+1x-j+1x−j+1 次,因此:

S(x)=∑j=1xd[j]⋅(x+1−j)S(x) = \sum_{j=1}^x d[j] \cdot (x+1-j)S(x)=∑j=1xd[j]⋅(x+1−j)

展开拆分公式:

S(x)=(x+1)∑j=1xd[j]−∑j=1xj⋅d[j]S(x) = (x+1)\sum_{j=1}^x d[j] - \sum_{j=1}^x j\cdot d[j]S(x)=(x+1)∑j=1xd[j]−∑j=1xj⋅d[j]

结论:必须维护两个数组

从最终公式可以看出,想要计算原数组的前缀和,需要两个前缀和结果:

  1. ∑d[j]\sum d[j]∑d[j]:差分数组的前缀和;

  2. ∑j⋅d[j]\sum j\cdot d[j]∑j⋅d[j]:下标乘差分数组的前缀和。

因此我们用两个树状数组分别维护:

  • tr1 :维护差分数组 d[i]d[i]d[i];

  • tr2 :维护加权差分数组 i⋅d[i]i\cdot d[i]i⋅d[i]。

四、重点精讲:双树状数组的更新规则

这是90%教程跳过的核心细节!我们已知区间修改 [l,r]+k[l,r]+k[l,r]+k 只会修改差分数组的两个位置:d[l]+kd[l]+kd[l]+k、d[r+1]−kd[r+1]-kd[r+1]−k。

1. tr1 更新逻辑(常规)

tr1 直接维护 d[i]d[i]d[i],因此跟随差分规则单点修改:

Plain 复制代码
update(tr1, l, k)
update(tr1, r+1, -k)

2. tr2 更新逻辑(核心重点)

tr2 维护的是 i×d[i]i \times d[i]i×d[i],这是一个被动加权数组d[i] 的变化量,会被下标 i 加权放大

  • 位置 lll:d[l]+=kd[l] += kd[l]+=k → 加权值变化:l×kl \times kl×k → update(tr2, l, l*k)

  • 位置 r+1r+1r+1:d[r+1]−=kd[r+1] -= kd[r+1]−=k → 加权值变化:−(r+1)×k-(r+1) \times k−(r+1)×k → update(tr2, r+1, -(r+1)*k)

五、区间查询公式实现

根据推导的前缀和公式,封装求 [1,x][1,x][1,x] 前缀和的逻辑,再通过「前缀和相减」得到任意区间和 [l,r][l,r][l,r]:

Plain 复制代码
# 求 [l, r] 区间和
def get(l, r):
    # S(r) = (r+1)*sum(d[1~r]) - sum(i*d[i][1~r])
    val_r = (r+1)*query(tr1,r) - query(tr2,r)
    # S(l-1) = l*sum(d[1~l-1]) - sum(i*d[i][1~l-1])
    val_l_1 = l*query(tr1,l-1) - query(tr2,l-1) 
    return val_r - val_l_1

六、完整实战代码

适配洛谷P3372【模板】线段树1(区间修改、区间查询),采用极简全局函数风格、快速读入、批量输出,无冗余代码,时间复杂度最优 O((n+m)log⁡n)O((n+m)\log n)O((n+m)logn)。

Plain 复制代码
import sys
input = lambda: sys.stdin.readline().strip()

# 树状数组核心基础函数
def lowbit(x):
    return x & -x

def update(tr, x, k):
    while x <= n:
        tr[x] += k
        x += lowbit(x)

def query(tr, x):
    res = 0
    while x > 0:
        res += tr[x]
        x -= lowbit(x)
    return res

# 计算区间 [l, r] 的和
def get(l, r):
    val_r = (r+1)*query(tr1,r) - query(tr2,r)
    val_l_1 = l*query(tr1,l-1) - query(tr2,l-1) 
    return val_r - val_l_1

# 主程序
n, m = map(int, input().split())
a = list(map(int, input().split()))
a = [0] + a  # 转为1下标,适配树状数组

# 初始化两个树状数组
tr1 = [0] * (n+2)
tr2 = [0] * (n+2)

# 构建差分数组,初始化双树状数组
for i in range(1, n+1):
    dif = a[i] - a[i-1]
    update(tr1, i, dif)
    update(tr2, i, i * dif)

ans = []
for _ in range(m):
    op = list(map(int, input().split()))
    if op[0] == 1:
        # 区间修改:[x,y] 整体加 k
        x, y, k = op[1], op[2], op[3]
        update(tr1, x, k)
        update(tr1, y+1, -k)
        # 重点:加权差分树状数组更新规则
        update(tr2, x, x*k)
        update(tr2, y+1, -(y+1)*k)
    else:
        # 区间查询:输出[x,y]区间和
        x, y = op[1], op[2]
        ans.append(str(get(x, y)))

# 批量输出,避免Python超时
print('\n'.join(ans))

七、复杂度与核心总结

1. 时间复杂度

  • 初始化:O(nlog⁡n)O(n\log n)O(nlogn);

  • 单次区间修改/区间查询:O(log⁡n)O(\log n)O(logn);

  • 整体复杂度:O((n+m)log⁡n)O((n+m)\log n)O((n+m)logn),完全通过洛谷1e5数据范围,无超时风险。

2. 全套树状数组体系闭环

  1. 单点改、区间查:单树状数组维护原数组;

  2. 区间改、单点查:单树状数组维护差分数组;

  3. 区间改、区间查:双树状数组维护「差分、加权差分」。

3. 核心精髓

树状数组本身只擅长单点修改+前缀查询,所有区间操作的能力,全部来自差分降维;而双树状数组的额外加权更新,只是为了抵消双重求和的权重,完美适配区间查询需求。

相关推荐
图码11 小时前
二分查找进阶:如何在有序数组中快速找到Upper Bound?
数据结构·算法·面试·分类·柔性数组
试剂界的爱马仕11 小时前
《古董局·终局5:潮生》第 2 章:镜子的天赋
大数据·人工智能·算法
码界筑梦坊11 小时前
133-基于Python的全球城市生活成本数据可视化分析系统
开发语言·python·信息可视化·django·毕业设计·生活
北小菜11 小时前
xclabel是一款开源图像标注与模型训练工具,采用Python+Flask开发,跨平台支持Windows/Linux/Mac
python·神经网络·计算机视觉·labelme·视频行为分析系统
徐安安ye11 小时前
FlashAttention在昇腾NPU上的性能实测:数据、瓶颈与优化上限
python·transformer
YsyaaabB11 小时前
ACM 模式通用代码模板
java·c++·python·算法
ComputerInBook11 小时前
Euclid 几何变换——仿射(affine)变换
算法·仿射变换·几何变换
hnxaoli12 小时前
统信小程序(十三)循环键鼠操作程序
python·小程序