最近本蒟蒻学了树状数组,很喜欢这个数据结构因为这代码确实短。
好了废话不多说直接正题。
树状数组是一种新颖的数据结构,这个数据结构1997年才发明,很年轻的一个数据结构。
这里说一个位运算lowbit,lowbit(x)表示取出x的最后一位1的数值。lowbit(x)=x&-x
。
先来一张手绘图形成一个直观的认识:(纯手绘勿喷)
图中a数组就是底层的那个12345678,树状数组就是飘在空中的12345678,我这样描述是不是有点那啥。
好了那这个东西是干啥的呢?
当我们要求出一个不断变化的连续一段区间的和时就会用到它。
所以引例:
问题描述
假设有一列数{Ai}(1≤i≤n),支持如下两种操作:
将Ak的值加D。(k, D是输入的数)
输出As+As+1+…+At。(s, t都是输入的数,S≤T)
输入格式
第一行一个整数n,
第二行为n个整数,表示{Ai}的初始值≤10000。
第三行为一个整数m,表示操作数
下接m行,每行描述一个操作,有如下两种情况:
ADD k d (表示将Ak加d,1<=k<=n,d为数,d的绝对值不超过10000)
SUM s t (表示输出As+…+At)
输出格式
对于每一个SUM提问,输出结果
树状数组的模板题QAQ。
前缀和什么的直接TLE,可以使用树状数组。
当然这道题也可以线段树,但是常数比树状数组高很多。
把图再放一遍:
c数组每个元素向下连接的线段,代表它的值是这些线段的另一端元素的和。
例如图中 c 1 = a 1 , c 2 = c 1 + a 2 , c 4 = c 2 + c 3 + a 4 c_1=a_1,c_2=c_1+a_2,c_4=c_2+c_3+a_4 c1=a1,c2=c1+a2,c4=c2+c3+a4。
那么这个东西怎么求和呢?
观察每个编号的二进制表示:
c
001
=
a
001
c_{001}=a_{001}
c001=a001
c
010
=
c
001
+
a
010
c_{010}=c_{001}+a_{010}
c010=c001+a010
c
011
=
a
011
c_{011}=a_{011}
c011=a011
c
100
=
c
010
+
c
011
+
a
100
c_{100}=c_{010}+c_{011}+a_{100}
c100=c010+c011+a100。
c
101
=
a
101
c_{101}=a_{101}
c101=a101
c
110
=
c
101
+
a
110
c_{110}=c_{101}+a_{110}
c110=c101+a110
c
111
=
a
111
c_{111}=a_{111}
c111=a111
c
1000
=
c
100
+
c
110
+
c
111
+
a
1000
c_{1000}=c_{100}+c_{110}+c_{111}+a_{1000}
c1000=c100+c110+c111+a1000
可以观察到是很有规律的。
显然,当 c i = a i c_i=a_i ci=ai时, l o w b i t ( i ) = 1 lowbit(i)=1 lowbit(i)=1。
再试着找规律,最后一个式子好像给了我们什么启发。
c i = c i − l o w b i t ( i ) + c i − l o w b i t ( i ) − l o w b i t ( i − l o w b i t ( i ) ) + c_i=c_{i-lowbit(i)}+c_{i-lowbit(i)-lowbit(i-lowbit(i))}+ ci=ci−lowbit(i)+ci−lowbit(i)−lowbit(i−lowbit(i))+… c 0 c_0 c0。
所以代码不难写出:
for (int i = x; i > 0; i -= lowbit(i)) c[x] += c[i];
这段代码能求出 c x c_x cx 的值。
好了还是没有说怎么求和啊……
观察可知, c x = ∑ i = x − l o w b i t ( x ) + 1 x a c_x=\sum^x_{i=x - lowbit(x)+1}a cx=∑i=x−lowbit(x)+1xa
所以 ∑ i = 1 r a = c r + c r − l o w b i t ( r ) + \sum^r_{i=1}a=c_r+c_{r-lowbit(r)}+ ∑i=1ra=cr+cr−lowbit(r)+…… c 0 c_0 c0。
代码也不难写出了:
inline int GetSum(int x)//求a[1]~a[x]的和
{
int sum = 0;
for (int i = x; i > 0; i -= lowbit(i)) sum += c[i];
return sum;
}
上面的所有代码不理解也要背会,只要背会了还是能解决树状数组的题目。
SUM操作我们就完成了,那么怎么完成ADD操作呢?
其实我们根本不用理 a a a 数组,直接更新树状数组 c c c 数组即可。
根据上面推出 c x c_x cx 的式子或观察每个 c i c_i ci 会影响到的其他 c i c_i ci,代码不难写出:
inline void modify(int x, int d)//将c[x]加上d并更新树状数组
{
for (int i = x; i <= n; i += lowbit(i)) c[i] += d;
}
同样,上面的代码不理解也要背会。但还是希望大家勤于观察找规律,理解这些代码背后的原理。
这样,不难写出本题的完整程序了:
#include <cstdio>
#define lowbit(x) (x & -x)
int c[100005], n;
inline void modify (int x, int d)
{
for (int i = x; i <= n; i += lowbit(i)) c[i] += d;
}
inline int GetSum (int x)
{
int sum = 0;
for (int i = x; i; i -= lowbit(i)) sum += c[i];
return sum;
}
inline int Getchar()
{
char ch;
while ((ch = getchar()) == '\n' || ch == ' ');
return ch;
}//这题输入是真的坑,我也不知道怎么scanf就死了
int main()
{
int q, t, a, b;
char ch;
scanf("%d", &n);
for (int i = 1; i <= n; i ++)
{
scanf("%d", &t);//本题的细节,输入a[i]可以理解为将c[i]加上a[i]
modify(i, t);
}
scanf("%d", &q);
getchar();
while (q --)
{
ch = Getchar();
getchar();//输入恶心死了,只需要读取首字母就能判断什么操作
getchar();
scanf("%d%d", &a, &b);
if (ch == 'S')
printf("%d\n", GetSum(b) - GetSum(a - 1));
else
modify(a, b);
}
}
好了树状数组的初步讲解就到这里了QAQ。
End