当前位置: 首页 > news >正文

普通树状数组

树状数组

普通的树状数组维护的信息及运算要满足 结合律可差分,如加法、乘法、异或等。

Q1:为什么要满足 结合律

A1:说明要求的区间信息可能是由若干个已知区间信息拼凑起来的,故需要满足结合律。

Q2:为什么要满足 可差分

A2:说明要求的区间信息可能通过两个区间作差得出。

一维树状数组

不妨假设 aaa 是原始序列,以往我们要求 aaa 的区间 [l,r][l,r][l,r] 的信息,需要遍历区间。

树状数组的核心是维护 若干个块状 的信息,然后将这若干个块状信息 结合 为我们需要的区间信息。

也就是 一个位置表达更多的信息,这样才能达到减少时间复杂度的效果。

怎么选择块的大小呢?

已知任意一个正整数 iii 都能被写成 二进制 的形式。

不妨设 iii 的二进制可以写成 2k1+2k2+⋯+2km2^{k_1}+2^{k_2}+\cdots+2^{k_m}2k1+2k2++2kmk1<k2<⋯<kmk_1 <k_2<\cdots<k_mk1<k2<<km)。

这样可以看做一个长度为 iii 区间的信息 被分成了 mmm 信息。

所以,如果要求 1∼i1\sim i1i 的区间信息,只需要知道这 mmm 个块的信息就可以。

怎么维护这 mmm 块信息呢?

可以令 tree[i]tree[i]tree[i] 维护一个以 iii右端点大小为 2k12^{k_1}2k1 的区间信息。

此时就剩下了 m−1m-1m1 块区间。

j=i−2k1j=i-2^{k_1}j=i2k1,不难发现 jjj 的二进制表示恰好为 2k2+⋯+2km2^{k_2}+\cdots+2^{k_m}2k2++2km

继续令 tree[j]tree[j]tree[j] 维护一个以 jjj 为右端点,大小为 2k22^{k_2}2k2 的区间。

而对于一个位置,只令其维护其 二进制最小的 111 所代表的长度 的区间信息,这个值被称为 lowbitlowbitlowbit

不难看出,只需要遍历 mmmtreetreetree 的位置就能拼凑出 [1,i][1,i][1,i] 的区间信息。

而遍历的方向就是,不断地用 iii 减去其 lowbitlowbitlowbit

又因为 m≤log(i)m\le log(i)mlog(i),所以求出 [1,i][1,i][1,i] 的区间信息只需要 O(logn)O(logn)O(logn) 的复杂度。

于是得到了树状数组最核心的 状态设计

tree[i]tree[i]tree[i] 维护的是以 iii 为右端点,大小为 lowbit(i)lowbit(i)lowbit(i) 的区间信息。

区间查询

要求 [l,r][l,r][l,r] 的区间信息,可以先求出 [1,r],[1,l][1,r],[1,l][1,r],[1,l] 的区间信息,然后再作差。

而求出 [1,i][1,i][1,i] 的区间信息,需要从 iii 开始往下跳 log(i)log(i)log(i) 次,直到跳到 000

T getsum(int x) {T ans = 0;while (x) {ans += tree[x];x = x - lowbit(x);}return ans;
}

单点修改

要修改 a[x]a[x]a[x] 的值,就需要在树状数组中同步修改能管辖到 xxx 的所有位置。

因为 xxx 是最小的能管辖到 xxx 的位置,所以从 xxx 开始一直往上跳,直到跳出范围即可。

nnn 表示 aaa 数组的大小,不难写出单点修改 a[x]a[x]a[x] 的过程:

#include <bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define int long long
#define endl '\n'
#define PII pair<int,int>
#define INF 1e18template<typename T>// 单点修改和区间查询的树状数组
struct Fenwick {vector <T> tree;int n;Fenwick(int _n) : n(_n), tree(_n + 2){}// 求 1~x 的前缀和T getsum(int x) {T ans = 0;int L = x;while (x) {ans += tree[x];x = x - lowbit(x);}return ans;}// 区间查询T get_range_sum(int l, int r) {return getsum(r) - getsum(l - 1);}// 单点修改,是为了 range_add 服务void add(int x, T k) {while (x <= n) {tree[x] += k;x += lowbit(x);}}static inline int lowbit(int x) {return x & (-x);}
};
void slove () {Fenwick<int> tree(n);
}signed main () {ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);slove();
}

带区间加的区间和

如何给区间 [l,r][l,r][l,r] 都加上一个常数 kkk

显然不能使用 r−l+1r-l+1rl+1 次单点修改,这样时间复杂度比直接遍历还劣。

考虑到实现单点修改是 O(logn)O(logn)O(logn),实现区间查询是 O(logn)O(logn)O(logn)

不妨 用单点修改来代替区间加

给区间加上一个常数可以用 差分 解决,即给 lll 处加 kkkr+1r+1r+1 处减 kkk

如果此时再去求区间和,应该怎么办?

考虑查询 [1,l][1,l][1,l] 的区间和,则有
∑i=1l∑j=1idj \sum_{i=1}^{l}\sum_{j=1}^{i}d_j i=1lj=1idj
等价于
∑i=1ldi×(l−i+1)=(l+1)∑i=1l×di−∑i=1li×di \sum_{i=1}^{l}d_i\times(l-i+1)=(l+1)\sum_{i=1}^{l}\times d_i-\sum_{i=1}^{l}i\times d_i i=1ldi×(li+1)=(l+1)i=1l×dii=1li×di
能转换为 ∑i=1ldi×(l−i+1)\sum_{i=1}^{l}d_i\times(l-i+1)i=1ldi×(li+1) 是因为每个 did_idi 都被加了 l−i+1l-i+1li+1 次。

然后拆开括号得到 (l+1)∑i=1l×di(l+1)\sum_{i=1}^{l}\times d_i(l+1)i=1l×di∑i=1li×di\sum_{i=1}^{l}i\times d_ii=1li×di

拆开是因为一个树状数组是 无法同时维护 包含 两个变量 l,il,il,i 的表达式。

此时就只需要维护 did_idici=i×dic_i=i\times d_ici=i×di 这两个数组。

#include <bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define int long long
#define endl '\n'
#define PII pair<int,int>
#define INF 1e18template<typename T>// 区间修改 + 区间查询
struct Fenwick {vector <T> tree_d, tree_dx;int n;Fenwick(int _n) : n(_n), tree_d(_n + 2), tree_dx(_n + 2){}// 求 1~x 的前缀和T getsum(int x) {T ans1 = 0, ans2 = 0;int L = x;while (x) {ans1 += tree_d[x];ans2 += tree_dx[x];x = x - lowbit(x);}return (L + 1) * ans1 - ans2;}// 暴露在外的接口,求范围值T get_range_sum(int l, int r) {return getsum(r) - getsum(l - 1);}// 单点修改,是为了 range_add 服务void add(int x, T k) {while (x <= n) {tree_d[x] += k;tree_dx[x] += k * x;x += lowbit(x);}}// 暴露在外的接口,给区间 [l, r] 加上 xvoid range_add(int l, int r, T x) {add(l, x);add(r + 1, -x);}static inline int lowbit(int x) {return x & (-x);}
};
void slove () {Fenwick<int> tree(n);
}signed main () {ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);slove();
}
http://www.dtcms.com/a/315754.html

相关文章:

  • 《Node.js与 Elasticsearch的全文搜索架构解析》
  • Leetcode 13 java
  • 2025-08-05Gitee + PicGo + Typora搭建免费图床
  • MongoDB学习专题(二)核心操作
  • MongoDB 从3.4.0升级到4.0.0完整指南实战-优雅草蜻蜓I即时通讯水银版成功升级-卓伊凡|bigniu
  • 时序数据库flux aggregateWindow命令详解
  • Baumer相机如何通过YoloV8深度学习模型实现道路场所路人口罩的检测识别(C#代码UI界面版)
  • 概率论之条件概率
  • ubuntu自动重启BUG排查指南
  • C++ - 仿 RabbitMQ 实现消息队列--服务端核心模块实现(六)
  • Go 单元测试:如何只运行某个测试函数(精确控制)
  • C++ 网络编程入门:TCP 协议下的简易计算器项目
  • 【STM32】HAL库中的实现(四):RTC (实时时钟)
  • 日语学习-日语知识点小记-构建基础-JLPT-N3阶段(14):文法:ていく+きた+单词
  • MQTT学习
  • Starrocks 关于 trace 命令的说明
  • C# --- 本地缓存失效形成缓存击穿触发限流
  • 【面向对象】面向对象七大原则
  • 【乐企板式文件生成工程】关于乐企板式文件(PDF/OFD/XML)生成工程介绍
  • [2401MT-B] 面积比较
  • 翻译的本质:人工翻译vs机器翻译的核心差异与互补性
  • Starrocks中的 Query Profile以及explain analyze及trace命令中的区别
  • MySQL 中 VARCHAR 和 TEXT 的区别
  • 智慧酒店:科技赋能下的未来住宿新体验
  • Spring-rabbit使用实战六
  • 国产三防平板电脑是什么?三防平板推荐
  • Spark内核调度
  • RTC实时时钟RX8900SA国产替代FRTC8900S
  • 使用maven-shade-plugin解决es跨版本冲突
  • 微信小程序功能实现:页面导航与跳转