树状数组 修改及查询操作

树状数组 修改及查询操作

常见问题:高效率地查询和维护前缀和(或区间和)。

如果数列为静态的,预处理前缀和即可。

如果数列为动态的,改变任意一个元素 ak 的值,都会影响后续前缀和的值。

树状数组可以有效解决此类问题。


lowbit操作

lowbit就是对于十进制数 x ,有

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操作就是找到 x 的二进制数的最后一个1,其余全抹成0

该操作是为了爬链,下面的图展示了树状数组修改和查询。

参考:【董晓算法 C81【模板】树状数组 点修+区查 区修+点查】https://www.bilibili.com/video/BV17N4y1x7c6?vd_source=d99da713618691ba36ec8e0d718ce6e7


单点修改 + 区间查询

P3374 【模板】树状数组 1

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  • 将某一个数加上 x

  • 求出某区间每一个数的和

输入格式

第一行包含两个正整数 n,m,分别表示该数列数字的个数和操作的总个数。

第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。

接下来 m 行每行包含 3 个整数,表示一个操作,具体如下:

  • 1 x k 含义:将第 x 个数加上 k

  • 2 x y 含义:输出区间 [x,y] 内每个数的和

输出格式

输出包含若干行整数,即为所有操作 2 的结果。

输入输出样例 #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

说明/提示

【数据范围】

对于 30% 的数据,1n81m10
对于 70% 的数据,1n,m104
对于 100% 的数据,1n,m5×105

数据保证对于任意时刻,a 的任意子区间(包括长度为 1n 的子区间)和均在 [231,231) 范围内。

样例说明:

故输出结果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] 一个简单的整数问题

题目描述

给定长度为 N 的数列 A,然后输入 M 行操作指令。

第一类指令形如 C l r d,表示把数列中第 lr 个数都加 d

第二类指令形如 Q x,表示询问数列中第 x 个数的值。

对于每个询问,输出一个整数表示答案。

输入格式

第一行包含两个整数 NM

第二行包含 N 个整数 A[i]

接下来 M 行表示 M 条指令,每条指令的格式如题目描述所示。

输出格式

对于每个询问,输出一个整数表示答案。

每个答案占一行。

数据范围

1N,M105,
|d|10000,
|A[i]|109

输入样例:

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

思路

我们知道直接对数组 a[i] 求前缀和是求区间和,那么对差分数组 D[i] 求前缀和就是求 a[i] 本身。

比如说给定区间 [l,r],在区间内每个数加上 d,那么就直接把差分数组 D[l] 加上 dD[r+1] 减去 d

这样既保证了区间内的修改,有保证了区间外的稳定性。

但可以发现,没有用到差分数组,直接修改这一个区间的前缀和,实际一样的效果。

最后单点查询,直接输出该点所对应差分数组的前缀和就行。

代码

#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

题目描述

给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:

  1. C l r d,表示把 A[l],A[l+1],,A[r] 都加上 d
  2. Q l r,表示询问数列中第 lr 个数的和。

对于每个询问,输出一个整数表示答案。

输入格式

第一行两个整数 N,M

第二行 N 个整数 A[i]

接下来 M 行表示 M 条指令,每条指令的格式如题目描述所示。

输出格式

对于每个询问,输出一个整数表示答案。

每个答案占一行。

数据范围

1N,M105,
|d|10000,
|A[i]|109

输入样例:

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

思路

这里需要公式推导。

给定一个数 k ,要求计算 a1ak 所有数之和。

 a1+a2++ak= D1+(D1+D2)+(D1+D2+D3)++(D1+D2++Dk)= kD1+(k1)D2+(k2)D3++(k(k1))Dk= k(D1+D2++Dk)(D2+2D3++(k1)Dk)= ki=1kDii=1k(i1)Di

这里我们需要两个树状数组维护,称为二阶树状数组

ki=1kDi 称为 s1 数组,i=1k(i1)Di 成为 s2 数组。

在更改区间时,两个树状数组同时更新。

注意 s2 的更新,更新左端点 l 时,加上 (l1)×d;更新右端点 r 时,减去 (r+11)×d=r×d

同时注意维护的单点是 lr+1

查询时输出 a1ar 所有数之和 a1al1 所有数之和。

!!!(更新区间是 lr+1 ,最后输出是 l1r,一定注意)

代码

#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;
}
posted @   AKgrid  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示