现在我们有一个数组,我们需要为这个数组写两个函数。(且需要处理上百万的数据)
我们很容易想到使用暴力遍历 [1, n] 的元素并求和写出。
本文到此结束。
但是太暴力了。在百万的数据量面前显得太不合适了。
有人可能想着这样去优化:
把数据两两求和,保存在另一个数组里面,用空间换时间。如果求某 n 个元素的和,我们就可以把时间优化为 n/2 ,节省一半的时间。而且在修改某一个元素时,也只需要多修改一个数字而已。
但是这样子还是很慢。我们可以多建几层,将元素两两求和。一直计算直至只剩下一个元素为止 ,线段树由此而来
但是我们计算时发现,在这一棵树里面,还有很多的数据没有用上,比如
这些数据在计算时,完全不会使用到,因为有比他们更加好的选择,比如在计算 n = 7 时,只需要将红色圈圈里面的值相加即可得到。
发现一些没有用的数据之后,我们将这些没用的数据去除,得到了以下结果。
通过观察我们发现,剩下的数据个数刚好为 n 个,也就是最开始的数组大小,这 就是一棵树状数组。
上面数组的每一个元素都代表着一个底下的区间,而下面每一个区间都代表着一段区间的和。求和时,我们只需要找到对应的区间,将区间求和即可
求和之前已经展示过 n = 7 的求和了。而在修改时,需要将上层的每一个区间修改。比如修改 n = 6这个元素。
接下来是Java代码。
看上去很复杂吧,需要每次都找到对应的区间,感觉找到这些规律都复杂的不得了,但是代码如下
private static int bitlow(int x) {
return x & -x;
}
没错,这就是找到对应区间的代码。
// lowBit函数会求出一个二进制数字的最低位代表哪个数字 // 如 100010 这个二进制数的 lowBit -> 2,因为二进制最后的 1 在倒数第二位 static int lowbit(int x) { return x & -x; }
lowbit 值刚好等于区间对应的长度,为我们修改值时找上面一个区间提供了非常便捷的方法,只要加上 Lowbit 的值,就可以直接找到上面的区间对应的下标。在求和时,也只需要减去 lowbit 值就可以找到需要添加的区间对应的下标。
完整代码
public class Main {
static long[] b;
static int N;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
int m = sc.nextInt(), x, y, t;
b = new long[N + 1];
for (int i = 1; i <= N; i++)
add(i, sc.nextInt());
for (int i = 0; i < m; i++) {
t = sc.nextInt();
x = sc.nextInt();
y = sc.nextInt();
if (t == 1)
add(x, y);
else
System.out.println(count(y) - count(x - 1));
}
}
// lowBit函数会求出一个二进制数字的最低位代表哪个数字
// 如 100010 这个二进制数的 lowBit -> 2,因为二进制最后的 1 在倒数第二位
static int lowbit(int x) {
return x & -x;
}
static void add(int p, int k) {
while (p <= N) {
b[p] += k;
p += lowbit(p);
}
}
static long count(int p) {
long result = 0;
while (p > 0) {
result += b[p];
p -= lowbit(p);
}
return result;
}
}
看到这里还不懂我推荐我当时看的视频,真的超级丝滑
【五分钟丝滑动画讲解 | 树状数组】