树状数组(待补)(生硬 公式 用法 证明)
理解之后挺简单的。开始比较抽象,理解后比较简单,不如直接看代码。
树状数组是一个兼顾修改和查询的数据结构。一般可以支持,
因为常数小,在某些情况下比较好用,但一般它能做到的,线段树都能做到。
中心思想
关于树状数组的一切因为涉及 lowbit 比较抽象。
核心就是
我们实质上是把这个数组拆分成了一个树,但计算原理运行上和树没什么关系。只是呈树形关系。关于原理见这篇文章 让你顿悟树状数组原理与由来 - 知乎 (zhihu.com)
查询
我们知道一个数有二进制的形式,如
树状数组就是按照 tr 数组的形式来拆分了区间,像
对于一个区间可以不断先加上
而
如果想求任意区间,方法和前缀和求区间方式一样。
int sum(int x) // 求出为第1 - x的和 sum/query { int res = 0; for (int i = x; i; i -= lowbit(i)) res += tr[i]; return res; }
关系
证明待补。自己想想也简单。
修改
如果第 i 个数加上了 k,那么所有包含 i 的 tr 都应该加上 k,可写出。
void add(int x, int k) // 让第i个数加上k { for (int i = x; i <= n; i += lowbit(i)) tr[i] += k; }
求出 tr 数组
我们求出的方式以
tr[10100] = [10001,10100] 10011 = 10100 - 1 10010 = 10011 - lowbit(10011) = 10011 - 1 10010 - lowbit(10010) = 10010 - 10 = 10000 不属于 [10001,10100] tr[10011] = [10011,10011] tr[10010] = [10001,10010] tr[10100] = a[10100] + tr[10011] + tr[10010]
可以发现就是在不超过
代码如此
for (int i = 1; i <= n; i ++ ) tr[i] = a[i]; // 先赋值a[i] for (int x = 1; x <= n; x ++ ) for (int i = x - 1; i >= x - lowbit(x) + 1; i -= lowbit(i)) tr[x] += tr[i];
代码
初始化
// 初始化 int a[], tr[], sum[]; // a[]原数组 tr[]树状数组 sum[]前缀和 int lowbit(int x) { return x & -x; } void add(int x, int k) { for (int i = x; i <= n; i + lowbit(i)) tr[i] += k; } // (1) O(nlogn) 常用 使用修改的方式 for (int i = 1; i <= n; i ++ ) add(i, a[i]); // (2) O(n) 根据边 for (int i = 1; i <= n; i ++ ) tr[i] = a[i]; // 先赋值a[i] for (int x = 1; x <= n; x ++ ) for (int i = x - 1; i >= x - lowbit(x) + 1; i -= lowbit(i)) tr[x] += tr[i]; // (3) O(n) 根据原理 for (int i = 1; i <= n; i ++ ) sum[i] = sum[i - 1] + a[i]; for (int i = 1; i <= n; i ++ ) tr[i] = sum[i] - sum[i - lowbit(i)];
修改/查询
void add(int x, int k) // 让第x个数加上k { for (int i = x; i <= n; i += lowbit(i)) tr[i] += k; } int sum(int x) // 求出为第1 - x的和 { int res = 0; for (int i = x; i; i -= lowbit(i)) res += tr[i]; return res; }
应用
特殊的应用,比如支持区间修改 + 区间查询。这个的常数是对应线段树的四分之一。
![[Pasted image 20241125081846.png|303]]
差分 + 树状数组可以把区间修改优化到O(logn)
区间和
如这题 P11217 【MX-S4-T1】「yyOI R2」youyou 的垃圾桶 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 如果使用线段树的话
代码
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long LL ; const int N = 100010; int n, m; int a[N]; LL tr1[N]; // b[i] 的前缀和 LL tr2[N]; // b[i] * i 的前缀和 int lowbit(int x) { return x & -x; } void add(LL tr[], int x, LL k) { for (int i = x; i <= n; i += lowbit(i)) tr[i] += k; } LL sum(LL tr[], int x) { LL res = 0; for (int i = x; i; i -= lowbit(i)) res += tr[i]; return res; } LL p_sum(int x) { return sum(tr1, x) * (x + 1) - sum(tr2, x); } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]); for (int i = 1; i <= n; i ++ ) { int b = a[i] - a[i - 1]; add(tr1, i, b); add(tr2, i, (LL)b * i); } while (m -- ) { int l, r, d; char op[2]; scanf("%s%d%d", op, &l, &r); if (op[0] == 'C') { scanf("%d", &d); add(tr1, l, d), add(tr2, l, l * d); add(tr1, r + 1, -d), add(tr2, r + 1, (r + 1) * -d); } else printf("%lld\n", p_sum(r) - p_sum(l - 1)); } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人