【总结】树状数组
树状数组的概念
树状数组(Binary Indexed Tree(B.I.T))是一个区间查询和单点修改复杂度都为
的数据结构。主要用于查询任意两点之间的所有元素之和。
引入
-
问题的提出
有一个一维数组,长度为 。
对这个数组做两种操作:- 修改,对第
之间的某元素增加 。 - 求和,求
到 的和。
- 修改,对第
-
朴素算法
-
用
for
循环从 到 依次求和,时间复杂度: -
缺陷:当数据规模极大的时候,将会变得效率低下。
-
-
前缀和
- 我们可以做到查询
, - 但是我们的修改依旧很慢
- 我们可以做到查询
-
我们可以采用树状数组
lowbit
lowbit(i)的意思是将
转化成二进制数之后,只保留最低位的 其后面的 ,截断前面的内容,然后再转成十进制数,这个数也是树状数组中 号位的子叶个数。
这里直接给出式子
lowbit(x) = x & (-x)
Build
我们可以采用一种类似
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
c[i] += a[i];
if (i + lowbit(i) <= n)
c[i + lowbit(i)] += c[i];
}
Update
该操作可以将
void update(int x, int k) {
while (x <= n) {
c[x] += k;
x += lowbit(x);
}
}
query
该操作可以求得
int query(int x) {
int ans = 0;
while (x) {
ans += c[x];
x -= lowbit(x);
}
return ans;
}
不要问我为什么要这张图放这么多次
推广
- 单点查询,区间修改
我们可以联想到一个叫差分的东西,我们可以维护一个差分数组,将区间查询,单点修改转化为单点查询,区间修改。
- 区间修改,区间查询
我们定义序列
进行推导
我们只需分别维护他们就可以了。
具体代码如下:
#include <cstdio>
#define int long long
#define lowbit(x) (x & (-x))
const int MAXN = 1e6 + 5;
int n, q;
int a[MAXN], c1[MAXN], c2[MAXN];
void Add(int x, int v) {
int v1 = v * x;
while (x <= n) {
c1[x] += v, c2[x] += v1;
x += lowbit(x);
}
}
int Sum(int* c, int x) {
int res = 0;
while (x) {
res += c[x];
x -= lowbit(x);
}
return res;
}
void update(int l, int r, int k) {
Add(l, k), Add(r + 1, -k);
}
int query(int l, int r) {
return (r + 1) * Sum(c1, r) - l * Sum(c1, l - 1) - (Sum(c2, r) - Sum(c2, l - 1));
}
signed main() {
scanf("%lld %lld", &n, &q);
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
update(i, i, a[i]);
}
while (q--) {
int op, x, y, k;
scanf("%lld %lld %lld", &op, &x, &y);
if (op == 1) {
scanf("%lld", &k);
update(x, y, k);
} else {
printf("%lld\n", query(x, y));
}
}
return 0;
}
我们可以类比普通的树状数组得到二维树状数组,但是对于这一系列操作,我们需要用到容斥原理。
- 二维树状数组:区间查询,单点修改
#include <cstdio>
#define int long long
#define lowbit(x) (x & (-x))
const int MAXN = (2 << 12) + 5;
int n, m, op, x, y, u, v, k;
int a[MAXN][MAXN];
int c[MAXN][MAXN];
void update(int x, int y, int k) {
for (int i = x; i <= n; i += lowbit(i))
for (int j = y; j <= m; j += lowbit(j))
c[i][j] += k;
}
int query(int x, int y) {
int res = 0;
for (int i = x; i > 0; i -= lowbit(i))
for (int j = y; j > 0; j -= lowbit(j))
res += c[i][j];
return res;
}
signed main() {
scanf("%lld %lld", &n, &m);
while (~scanf("%lld", &op)) {
if (op == 1) {
scanf("%lld %lld %lld", &x, &y, &k);
update(x, y, k);
} else {
scanf("%lld %lld %lld %lld", &x, &y, &u, &v);
printf("%lld\n", query(u, v) - query(u, y - 1) - query(x - 1, v) + query(x - 1, y - 1));
}
}
return 0;
}
- 二维树状数组:单点查询,区间修改
#include <cstdio>
#define int long long
#define lowbit(x) (x & (-x))
const int MAXN = 5005;
int n, m, op, x, y, u, v, k;
int a[MAXN][MAXN];
int c[MAXN][MAXN];
void update(int x, int y, int k) {
for (int i = x; i <= n; i += lowbit(i))
for (int j = y; j <= m; j += lowbit(j))
c[i][j] += k;
}
int query(int x, int y) {
int res = 0;
for (int i = x; i > 0; i -= lowbit(i))
for (int j = y; j > 0; j -= lowbit(j))
res += c[i][j];
return res;
}
signed main() {
scanf("%lld %lld", &n, &m);
while (~scanf("%lld", &op)) {
if (op == 1) {
scanf("%lld %lld %lld %lld %lld", &x, &y, &u, &v, &k);
update(u + 1, v + 1, k), update(x, y, k), update(x, v + 1, -k), update(u + 1, y, -k);
} else {
scanf("%lld %lld", &x, &y);
printf("%lld\n", query(x, y));
}
}
return 0;
}
- 二维树状数组:区间查询,区间修改
同一维树状数组,我们首先推一下式子
#include <cstdio>
#define int long long
#define lowbit(x) (x & (-x))
const int MAXN = 5005;
int n, m, op, x, y, u, v, k;
int a[MAXN][MAXN];
int c1[MAXN][MAXN], c2[MAXN][MAXN], c3[MAXN][MAXN], c4[MAXN][MAXN];
void update(int x, int y, int k) {
for (int i = x; i <= n; i += lowbit(i))
for (int j = y; j <= m; j += lowbit(j)) {
c1[i][j] += k;
c2[i][j] += x * k;
c3[i][j] += y * k;
c4[i][j] += x * y * k;
}
}
int query(int x, int y) {
int res = 0;
for (int i = x; i > 0; i -= lowbit(i))
for (int j = y; j > 0; j -= lowbit(j)) {
res += (x + 1) * (y + 1) * c1[i][j] - (y + 1) * c2[i][j] - (x + 1) * c3[i][j] + c4[i][j];
}
return res;
}
signed main() {
scanf("%lld %lld", &n, &m);
while (~scanf("%lld", &op)) {
if (op == 1) {
scanf("%lld %lld %lld %lld %lld", &x, &y, &u, &v, &k);
update(u + 1, v + 1, k), update(x, y, k), update(x, v + 1, -k), update(u + 1, y, -k);
} else {
scanf("%lld %lld %lld %lld", &x, &y, &u, &v);
printf("%lld\n", query(u, v) - query(u, y - 1) - query(x - 1, v) + query(x - 1, y - 1));
}
}
return 0;
}
例题
本文来自博客园,作者:zhou_ziyi,转载请注明原文链接:https://www.cnblogs.com/zhouziyi/p/16527213.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)