树状数组模板

树状数组是一种常用的数据结构,可以方便的单点修改、区间求和。
顾名思义,所谓树状数组,就是像树一样的数组。只不过这棵树有点“偏”。如图所示。
树状数组模板 - 卢凯宾 - 石门实验中学卢凯宾的博客
(图中白色的是原数组,红色的是树状数组。)
可以看到,每个红色结点的“管辖”范围是不同的,通过几条边与几个儿子连着。有的结点只管辖自己对应的数组那个下标的值(例如c[1],c[3],c[5],c[7]...),有的结点却管辖多个子结点的值。interesting!
那么,到底有什么规律捏?我们似乎可以观察发现,凡是只管辖单个结点的,都是 奇数下标的结点。
由此可以作出大胆的猜测:会不会跟二进制有关系呢?不妨列表观察:
 十进制数 对应的二进制数二进制数最右端第一个1的位置 
1
 210 
 311 
 4100 
 5101 
 6110 
 7111 
 81000 
 91001 
 101010 
显然对于树状数组c,c[i]可以管辖的元素数量取决于i对应的进制数最右端第一个1的位置。
至于到底怎么求一个二进制数最右端第一个1的位置呢?这里介绍一种名叫lowbit的高端技术。
lowbit(x) = x & (~(x - 1)) = x & -x
那么如何单点修改和区间求和呢?我们分别来看两个例子。
单点修改:例如我们要修改第3个点,根据上图可知a[3]受t[3]管辖,t[3]受t[4]管辖,t[4]受t[8]管辖,也就是说,修改t[3]的值的同时也要修改t[4]和t[8]的值。再借助上表我们可知其实就是每次把下标加上当前下标的lowbit。
区间求和:例如我们要求前6个点的和,根据上图可知应分成2个小区间:[1,4]和[5,6],这两个区间分别属于t[4]和t[6]管辖,同样借助上表我们可以观察出,从我们要求的点开始,每次取当前点的t[i]值累加,并把下标减去当前下标的lowbit,即可。
树状数组作为一种可以快速维护前缀和的数据结构,在某些时候是非常灵活也很好用的。但它的缺陷就在于适用面比较窄,特别相对于线段树而言。但它的好处是 好写啊! 好写啊!! 好写啊!!!代码复杂度非!常!低!!
不过,之前创新班也讲过树状数组,但因为个人比较懒的缘故,没有找相关题目刷,久而久之也就忘的差不多了。
今晚和yhf一起把树状数组 重新复习了一遍,写了一个模板。写的比较丑,留给自己以后复习的时候能看懂就行了。

//树状数组模板 by LKB 2016.8.4
//以下注释中涉及二进制数"第x位"的,
//均指该数的二进制形式右数第i位(从0开始算)
//例如11的二进制为1011,那么就有:
//11的二进制第0位为1,第1位为1,第2位为0,第3位为1
#include <iostream>

using namespace std;

const int maxn = 1000;

#define lowbit(x) (x & (-x))

int n, m;
int a[maxn]; //原始数组
int t[maxn]; //树状数组

int sum(int p) { //区间求和
//区间求和要把目标区间分成几个互相独立的小区间
//例如求sum(6),过程如下
//首先取t[6],它的取值范围只涉及a[6-lowbit(6)]..a[6]
//6的二进制是110,lowbit(6)=(10)2=2,故要取t[6-lowbit(6)]
//6-2=4的二进制是100,lowbit(4)=(100)2=4,4-lowbit(4)=0
//求和结束
int res = 0;
while (p) {
res += t[p]; //加上t[p]...t[lowbit(p)]的值
p -= lowbit(p); //指针左移lowbit(p)个位置
}
return res;
}

void add(int p, int v) { //单点修改
//例如修改第3个点,过程如下
//3的二进制是11,lowbit(3)=1
//3+lowbit(3)=4,故修改第4个点,4的二进制是100,lowbit(4)=4
//4+lowbit(4)=8,故修改第8个点,8的二进制是1000,
//....
while (p <= n) {
t[p] += v; //对涉及点p的所有点都修改
p += lowbit(p); //找下一个涉及点p的点
}
}

int main() {
cin >> n >> m;
for (int i = 0; i < m; i++) {
char act;
cin >> act;
if (act == 'A') { //Add单点修改
int pos, delta;
cin >> pos >> delta; //把第pos个点的值增加delta
add(pos, delta);
}
if (act == 'S') { //Sum区间求和
int start, end;
cin >> start >> end; //求[start, end]区间内值的和
cout << sum(end) - sum(start - 1) << endl;
//原理类似前缀和
}
}
return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值