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

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

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

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

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

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

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

二、前置知识回顾

1. 差分核心定义

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

两大核心性质:

  1. 区间修改:对 l,rl,rl,r 全体加 kkk,等价于两次单点修改:dl+=k、dr+1−=kdl+=k、dr+1-=kdl+=k、dr+1−=k;

  2. 单点查询:原数组单点值 ax=∑i=1xdiax = \sum_{i=1}^x diax=∑i=1xdi(差分数组前缀和)。

2. 旧版局限

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

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

我们的目标:求出原数组 a1∼xa1\\sim xa1∼x 的前缀和 S(x)=∑i=1xaiS(x) = \sum_{i=1}^x aiS(x)=∑i=1xai

由差分性质可知 ai=∑j=1idjai = \sum_{j=1}^i djai=∑j=1idj,将其代入前缀和公式,得到双重求和式

S(x)=∑i=1x∑j=1idjS(x) = \sum_{i=1}^x \sum_{j=1}^i djS(x)=∑i=1x∑j=1idj

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

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

S(x)=∑j=1xdj⋅(x+1−j)S(x) = \sum_{j=1}^x dj \cdot (x+1-j)S(x)=∑j=1xdj⋅(x+1−j)

展开拆分公式:

S(x)=(x+1)∑j=1xdj−∑j=1xj⋅djS(x) = (x+1)\sum_{j=1}^x dj - \sum_{j=1}^x j\cdot djS(x)=(x+1)∑j=1xdj−∑j=1xj⋅dj

结论:必须维护两个数组

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

  1. ∑dj\sum dj∑dj:差分数组的前缀和;

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

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

  • tr1 :维护差分数组 dididi

  • tr2 :维护加权差分数组 i⋅dii\cdot dii⋅di

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

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

1. tr1 更新逻辑(常规)

tr1 直接维护 dididi,因此跟随差分规则单点修改:

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

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

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

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

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

五、区间查询公式实现

根据推导的前缀和公式,封装求 1,x1,x1,x 前缀和的逻辑,再通过「前缀和相减」得到任意区间和 l,rl,rl,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. 核心精髓

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

相关推荐
菜鸟‍12 小时前
LeetCode 1 27 和 704 || 两数之和 移除元素 二分查找
算法·leetcode·职场和发展
财经资讯数据_灵砚智能13 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(夜间-次晨)2026年6月10日
大数据·人工智能·python·ai·信息可视化·自然语言处理·灵砚智能
namexingyun13 小时前
拆解Fable 5三重安全护栏:模型路由、蒸馏防护与生物安全分类器的技术原理 - 微元算力(weytoken)
java·人工智能·python·安全·架构·ai编程
chenment13 小时前
别再为每个模型单独写一套队列了:用 200 行代码封装多模态统一调用层
人工智能·python·产品
退休倒计时13 小时前
【每日一题】LeetCode 142. 环形链表 II TypeScript
算法·leetcode·链表·typescript
啊森要自信13 小时前
【GUI自动化测试】控件、鼠标键盘操作与多场景自动化
c语言·开发语言·python·adb·ipython
YJlio13 小时前
《Sysinternals实战指南》16.5 Ctrl2Cap 工具详解:把 Caps Lock 变成 Ctrl 的键盘改造与回退方法
linux·运维·服务器·网络·python·学习·计算机外设
某林21213 小时前
从底层硬件死锁到 QoS 通信底层的全链路复盘
python·ros2·qos
Jutick13 小时前
WebSocket 连接没断,行情却停了:如何给实时数据流加双层 watchdog?
python
石头城的小石头13 小时前
【从0到1的鼠标位置显示记录器,基于python环境pycharm下编译实施,最终打包为exe,欢迎交流】
python·目标跟踪·pycharm·计算机外设·鼠标