树状数组 修改及查询操作
树状数组 修改及查询操作
常见问题:高效率地查询和维护前缀和(或区间和)。
如果数列为静态的,预处理前缀和即可。
如果数列为动态的,改变任意一个元素
树状数组可以有效解决此类问题。
lowbit操作
lowbit就是对于十进制数
int lowbit(x) { return x & -x; }
比如 lowbit(20)
1 0 1 0 0 <- 20 & 0 1 1 0 0 <- -20 = 20各位取反 + 1 ------------------ 0 0 1 0 0 <- lowbit(20) = 4
可以发现,lowbit操作就是找到
该操作是为了爬链,下面的图展示了树状数组修改和查询。


参考:【董晓算法 C81【模板】树状数组 点修+区查 区修+点查】https://www.bilibili.com/video/BV17N4y1x7c6?vd_source=d99da713618691ba36ec8e0d718ce6e7
单点修改 + 区间查询
P3374 【模板】树状数组 1
题目描述
如题,已知一个数列,你需要进行下面两种操作:
-
将某一个数加上
-
求出某区间每一个数的和
输入格式
第一行包含两个正整数
第二行包含
接下来
-
1 x k
含义:将第 个数加上 -
2 x y
含义:输出区间 内每个数的和
输出格式
输出包含若干行整数,即为所有操作
输入输出样例 #1
输入 #1
5 5 1 5 4 2 3 1 1 3 2 2 5 1 3 -1 1 4 2 2 1 4
输出 #1
14 16
说明/提示
【数据范围】
对于
对于
对于
数据保证对于任意时刻,
样例说明:
故输出结果14、16
代码
// 树状数组单点修改 #include <bits/stdc++.h> using namespace std; using ll = long long; int n, m; const int N = 5e5 + 5; int a[N]; int s[N]; int lowbit(int x) { return x & -x; } void update(int x, int k)// 更新单点 { while (x <= n) {s[x] += k; x += lowbit(x);} } int sum(int x)// 输出前缀和 { int t = 0; while (x) {t += s[x], x -= lowbit(x);} return t; } int main() { cin >> n >> m; int op, x, y; // 初始化 for (int i = 1; i <= n; i++) { int a; cin >> a; update(i, a); } // m次操作 for (int i = 1; i <= m; i++) { cin >> op >> x >> y;; if (op == 1) update(x, y); else cout << sum(y) - sum(x - 1) << endl; } return 0; }
区间修改 + 单点查询
[242 AcWing] 一个简单的整数问题
题目描述
给定长度为
第一类指令形如 C l r d
,表示把数列中第
第二类指令形如 Q x
,表示询问数列中第
对于每个询问,输出一个整数表示答案。
输入格式
第一行包含两个整数
第二行包含
接下来
输出格式
对于每个询问,输出一个整数表示答案。
每个答案占一行。
数据范围
输入样例:
10 5 1 2 3 4 5 6 7 8 9 10 Q 4 Q 1 Q 2 C 1 6 3 Q 2
输出样例:
4 1 2 5
思路
我们知道直接对数组
比如说给定区间
这样既保证了区间内的修改,有保证了区间外的稳定性。
但可以发现,没有用到差分数组,直接修改这一个区间的前缀和,实际一样的效果。
最后单点查询,直接输出该点所对应差分数组的前缀和就行。
代码
#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 5; int a[N], s[N]; int n, m; int lowbit(int x) { return x & -x; } void update(int x, int d) { while (x <= n) s[x] += d, x += lowbit(x); } int sum(int x) { int t = a[x];// 注意这里t初始为a[x] while (x) t += s[x], x -= lowbit(x); return t; } int main() { cin >> n >> m; for (int i = 1; i <= n; i++) cin >> a[i]; while (m--) { char c; cin >> c; if (c == 'C') { int l, r, d; cin >> l >> r >> d; update(l, d); update(r + 1, -d); } else { int x; cin >> x; cout << sum(x) << endl; } } return 0; }
区间修改 + 区间查询
[243 AcWing] 一个简单的整数问题2
题目描述
给定一个长度为
C l r d
,表示把 都加上 。Q l r
,表示询问数列中第 个数的和。
对于每个询问,输出一个整数表示答案。
输入格式
第一行两个整数
第二行
接下来
输出格式
对于每个询问,输出一个整数表示答案。
每个答案占一行。
数据范围
输入样例:
10 5 1 2 3 4 5 6 7 8 9 10 Q 4 4 Q 1 10 Q 2 4 C 3 6 3 Q 2 4
输出样例:
4 55 9 15
思路
这里需要公式推导。
给定一个数
这里我们需要两个树状数组维护,称为二阶树状数组。
在更改区间时,两个树状数组同时更新。
注意
同时注意维护的单点是
查询时输出
!!!(更新区间是
代码
#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 5; typedef long long ll; #define lowbit(x) (x & -x) int n, m; ll s1[N], s2[N]; // 二阶树状数组 void update1(ll x, ll d) {while (x <= n) {s1[x] += d, x += lowbit(x);}} void update2(ll x, ll d) {while (x <= n) {s2[x] += d, x += lowbit(x);}} ll sum1(ll x) {auto t = 0ll; while (x) {t += s1[x], x -= lowbit(x);} return t;} ll sum2(ll x) {auto t = 0ll; while (x) {t += s2[x], x -= lowbit(x);} return t;} int main() { cin >> n >> m; ll old = 0, a; for (int i = 1; i <= n; i++) { cin >> a; update1(i, a - old); // 差分1 update2(i, (i - 1) * (a - old)); old = a; } while (m--) { char op; cin >> op; if (op == 'C') { int l, r, d; cin >> l >> r >> d; update1(l, d); update1(r + 1, -d); update2(l, d * (l - 1)); update2(r + 1, -d * r); // d * r = d * (r + 1 - 1) } else { int l, r; cin >> l >> r; cout << r * sum1(r) - (l - 1) * sum1(l - 1) - (sum2(r) - sum2(l - 1)) << endl; } } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】