最近本蒟蒻学了树状数组,很喜欢这个数据结构因为这代码确实短。
好了废话不多说直接正题。
树状数组是一种新颖的数据结构,这个数据结构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数组每个元素向下连接的线段,代表它的值是这些线段的另一端元素的和。
例如图中 。
那么这个东西怎么求和呢?
观察每个编号的二进制表示:
。
可以观察到是很有规律的。
显然,当时,。
再试着找规律,最后一个式子好像给了我们什么启发。
…。
所以代码不难写出:
for (int i = x; i > 0; i -= lowbit(i)) c[x] += c[i];
这段代码能求出
好了还是没有说怎么求和啊……
观察可知,
所以 ……。
代码也不难写出了:
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操作呢?
其实我们根本不用理 数组,直接更新树状数组
根据上面推出 的式子或观察每个 会影响到的其他 ,代码不难写出:
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