这里我们不描述其用法,在后面的题中详细描述
这是一道模板题。
给定数列 a[1], a[2], \dots, a[n] ,你需要依次进行 q 个操作,操作有两类:
1 i x
:给定 i,x ,将 a[i] 加上 x ;2 l r
:给定 l,r ,求 \sum_{i=l}^ra[i] 的值(换言之,求 a[l]+a[l+1]+\dots+a[r] 的值)。第一行包含 2 个正整数 n,q ,表示数列长度和询问个数。保证 1\le n,q\le 10^6 。
第二行 n 个整数 a[1], a[2], \dots, a[n] ,表示初始数列。保证 |a[i]|\le 10^6 。
接下来 q 行,每行一个操作,为以下两种之一:
1 i x
:给定 i,x ,将 a[i] 加上 x ;2 l r
:给定 l,r ,求 \sum_{i=l}^ra[i] 的值。保证 1\le l\le r\le n, |x|\le 10^6 。
对于每个 2 l r
操作输出一行,每行有一个整数,表示所求的结果。
3 2
1 2 3
1 2 0
2 1 3
6
这就是树状数组的雏形,我们可以清晰地看到。c[1]=a[1],c[2]=a[1]+a[2],c[3]=a[3],c[4]=a[1]+a[2]+a[3]+a[4],…
那么这种数据结构的储存究竟有什么规律呢 ?
我们将这些数字换做二进制补码看看
细心的同学们可以发现,每个ci的数的个数为2k
进一步,我们又可以发现每一个ci的二进制表示数从后往前依次数,直到出现的第一个1,取其以及其后的所有0,就是它应有的数的个数
int lowbit(int x) {
return x & (- x);
}
这种函数比较简单,但有些同学可能无法理解,举个例子
x=0010
-x=1010(原码)=1111(补码=~原码(符号位除外)+1)
x&(-x)=0010&1111=0010
如果连位运算都不知道的朋友,可以看看 这篇文章 QAQ
int lowbit(int x) {
return x - (x & (x - 1));
}
这里就不过多解释啦(:p)
我们回到这道题来,他要求能够单点修改,我们可以看到,如果修改了a1,那么受影响的只有c1、c2、c4和c8(比前缀和快得多)
然后我们又可以看到,c1c2差了lowbit(1),c2c4差了lowbit(2),c4c8差了lowbit(4),How wonderful!
void update(long long k, long long temp) {
for(int i = k; i <= n; i += lowbit(i)) {
bit[i] += temp;
}
}
其中k为起始位置,temp为修改的值,其余的就不多说了,大家可自己比划比划
求得了树状数组的值,我们该如何求前缀和呢
又来举个栗子(香啊)
我们想要知道 ∑ i = 1 5 a [ i ] \sum_{i=1}^{5}a[i] ∑i=15a[i]
但 ∑ i = 1 5 a [ i ] = c [ 5 ] + c [ 4 ] \sum_{i=1}^{5}a[i]=c[5]+c[4] ∑i=15a[i]=c[5]+c[4]
诶,5-4=lowbit(5),这是巧合吗,我们再举个例子
∑ i = 1 7 a [ i ] = c [ 7 ] + c [ 6 ] + c [ 4 ] \sum_{i=1}^{7}a[i]=c[7]+c[6]+c[4] ∑i=17a[i]=c[7]+c[6]+c[4]
7-6=lowbit(7),6-4=lowbit(6)
自然而然,我们就可以发现这个深藏不漏的求前缀和公式
long long Sum(long long k) {
long long sum = 0;
for(int i = k; i > 0; i -= lowbit(i)) {
sum += bit[i];
}
return sum;
}
#include
const int M = 1e6 + 5;
long long a[M], bit[M];
int n, m;
long long lowbit(long long k) {
return k & (- k);
}
void update(long long k, long long temp) {
for(int i = k; i <= n; i += lowbit(i)) {
bit[i] += temp;
}
}
long long Sum(long long k) {
long long sum = 0;
for(int i = k; i > 0; i -= lowbit(i)) {
sum += bit[i];
}
return sum;
}
int main() {
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i ++) {
scanf("%lld", &a[i]);
update(i, a[i]); //我们的a数组为0,所以每输入一个值,就相当于给为0的a[i]加上这个值
}
for(int i = 1; i <= m; i ++) {
int order;
scanf("%d", &order);
if(order == 1) {
long long x, k;
scanf("%lld %lld", &k, &x);
update(k, x);
}
if(order == 2) {
long long l, r;
scanf("%lld %lld", &l, &r);
printf("%lld\n", Sum(r) - Sum(l - 1));
}
}
return 0;
}
这是一道模板题。
给定数列 a[1], a[2], \dots, a[n] ,你需要依次进行 q 个操作,操作有两类:
1 l r x
:给定 l,r,x ,对于所有 i\in[l,r] ,将 a[i] 加上 x (换言之,将 a[l], a[l+1], \dots, a[r] 分别加上 x );2 i
:给定 i ,求 a[i] 的值。第一行包含 2 个正整数 n,q ,表示数列长度和询问个数。保证 1\le n,q\le 10^6 。
第二行 n 个整数 a[1], a[2], \dots, a[n] ,表示初始数列。保证 |a[i]|\le 10^6 。
接下来 q 行,每行一个操作,为以下两种之一:
1 l r x
:对于所有 i\in[l,r] ,将 a[i] 加上 x ;2 i
:给定 i ,求 a[i] 的值。保证 1\le l\le r\le n, |x|\le 10^6 。
对于每个 2 i
操作,输出一行,每行有一个整数,表示所求的结果。
3 2
1 2 3
1 1 3 0
2 2
2
对于所有数据, 1\le n,q\le 10^6, |a[i]|\le 10^6 , 1\le l\le r\le n, |x|\le 10^6 。
欲知后事如何,且听下回分解