P3374 【模板】树状数组 1 题解
P3374 【模板】树状数组 1 题解
简化题意
我们有一个长度为 \(n\) 的数组,现在要对其进行 \(m\) 次以下两种操作:
- 将一个数加上 \(x\)。
- 求出某个区间每一个数的和。
解题思路
这里,我们发现数据范围很大,直接暴力会超时。而如果采用前缀和,那么依然要进行值的修改,也不可取。
因此,我们要采用新的算法。
这里,引入树状数组。
树状数组算法利用了二进制的思想,这里我用一个例子帮助大家理解:
| 数字 | \(1\) | \(2\) | \(3\) | \(4\) | \(5\) | \(6\) | \(7\) | \(8\) |
|---|---|---|---|---|---|---|---|---|
| 二进制表达 | \(0001_{(2)}\) | \(0010_{(2)}\) | \(0011_{(2)}\) | \(0100_{(2)}\) | \(0101_{(2)}\) | \(0110_{(2)}\) | \(0111_{(2)}\) | \(1000_{(2)}\) |
如果我们要算 \(1 \sim 7\) 的和,怎么进行呢?
我们要创建一个数组 t,其中 \(t_i\) 代表数 \(x\) 到数 \(i\) 的和。这里的数 \(x\) 是什么呢?
借助上面的表格,我们指定 \(x\) 为 \(i\) 的二进制表示最低位为 \(1\) 时,\(x\) 的此位为 \(0\),且其他数位相同的值。
这个很难理解。我们看几个例子,比如 \(t_2\) 存储的就是 \(0 \sim 2\) 的和(\(\textcolor{red}{0}0_{(2)} \sim \textcolor{red}{1}0_{(2)}\));\(t_3\) 存储的就是 \(2 \sim 3\) 的和(\(1\textcolor{red}{0}_{(2)} \sim 1\textcolor{red}{1}_{(2)}\));\(t_6\) 存储的就是 \(4 \sim 6\) 的和(\(1\textcolor{red}{0}0_{(2)} \sim 1\textcolor{red}{1}0_{(2)}\))。
这个操作的代码实现并不难,但是要是看不懂的就难以理解。我们对于一个需要更新的数给定的下标 \(i\),不断执行 \(i += i \land (-i)\) 即可得出每个需要的数组了。比如我们经过操作,更新 \(t_1\),会同步更新 \(t_2\)、\(t_4\)、\(t_8\)。原理就是这个操作每次更新了 \(i\) 的需要的位置(如上),使其由 \(0\) 变为 \(1\),即达到了上述的意义。
程序实现:
void add(int pos, int x)//区间更新
{
while(pos <= n)
{
t[pos] += x;
pos += pos & -pos;//得到最低位
}
}
而区间求和又怎么实现呢?很简单,把刚才的区间更新操作反过来执行就行了。我们拿到一个数,就把它不断的最低位若为 \(1\),就去除再求和。具体操作看下面代码吧。
程序实现:
int sum(int pos)//区间求和
{
int s = 0;
while(pos)
{
s += t[pos];
pos -= pos & -pos;
}
return s;
}
这样,我们就可以显著降低操作所需时间了。
这篇文章写的比较仓促,主要是记录自己的学习经历。可能有表达不清,请见谅。
代码部分
#include <bits/stdc++.h>
using namespace std;
int n, m, t[500010];
void add(int pos, int x)//区间更新
{
while(pos <= n)
{
t[pos] += x;
pos += pos & -pos;//得到最低位
}
}
int sum(int pos)//区间求和
{
int s = 0;
while(pos)
{
s += t[pos];
pos -= pos & -pos;
}
return s;
}
void solve()
{
int type, a, b;
cin >> n >> m;
for(int i = 1;i <= n;i ++)
{
scanf("%d", &a);
add(i, a);
}
for(int i = 1;i <= m;i ++)
{
scanf("%d %d %d", &type, &a, &b);
if(type == 2)
printf("%d\n", sum(b) - sum(a - 1));
else
add(a, b);
}
}
int main()
{
solve();
return 0;
}
浙公网安备 33010602011771号