【模板篇】树状数组们(一)
以下文章逻辑混乱,请确保精神正常后再观看。
树状数组???
百度上讲的非常的多,各方面的资料也都有所涉及,大家不懂的可以去逛一圈搜一搜。
但树状数组能干的事情非常多,我看也没有很详细的总结,就来浪一波,斗胆讲一讲树状数组。。各位看官,要本蒟蒻讲的不好,你们轻喷。。
树状数组,作为一个nlogn数据结构,有着得天独厚 的优势。比如,常数小,码短好写。。
当然也是有一些缺点,比如很多事干不了。。然而在他能干的领域里面,效率能甩别的数据结构几条街。
为什么要搞树状数组???
快。
就拿树状数组最基础的例子来讲,让你维护一个数列,支持如下操作:
1)修改某个点i的值
2)查询区间[L,R]的数的总和
如果用数组来搞的话,会TLE的飞起。。。
这时候就需要一些nlogn的数据结构来救世了,比如:树状数组。
当然会有神犇@一些像线段树一类的数据结构,但树状数组比其他的要好写不少,常数也会小不少。。
然后树状数组最基本的原理是什么嘛,我真的讲不清楚(其实我不会),所以你们可以自己百度。
我只能说说大体意思。。
其实有张图会更明白是么。。(图片来自百度百科)
然后树状数组之流中有其特有的名片——lowbit~~
有了lowbit就知道是树状数组了。。。
lowbit是什么呢???
lowbit(i)指的是i在二进制表示下最低位的1及其后的所有0组成的值
比如36:在二进制下是100 1 00,最低位的1我加粗了,代表了4,所以lowbit(36)就是4。。
从图上可以看出,对于树状数组上的节点,第i个节点控制了(i-lowbit(i),i]的区间。。
比如节点1控制(0,1],节点4控制(0,4],(0是凑的),节点6控制(4,6](其实就是[5,6]啦)……
所以这么一联系就看出树状数组和lowbit密切相关。。
那如何求lowbit???
一位一位比嘛,不在乎那点效率,当然有快速的方法啦~
由百度百科(我说了你们听不懂):
设节点编号为x,那么这个节点管辖的区间为2^k(其中k为x二进制末尾0的个数)个元素。因为这个区间最后一个元素必然为Ax,
所以很明显:Cn = A(n – 2^k + 1) + … + An
这样算2^k易得(你要看不懂就不要管为什么了) lowbit(x)=x&(x^(x-1))①
然后计算机有个非常好的性质叫补码(就是一个二进制数相反数的表示),就是取反再加一。
所以①可简化为:lowbit(x)=x&-x !!!!真的好简便呢。。。
好吧,那么树状数组是怎么维护上面提到的两个问题的呢???
好吧,再去看图。。看图是有用的。。看到上面无比优美的性质,我们可以用C数组维护前缀和呢。。
没错,Ci可以保存1~i的前缀和,怎么求呢?
还记得大明湖畔的lowbit么?
(怎么不记得,他费了我半天劲才让我弄懂呢)
我们让一个点不断的减lowbit,然后把遇到的点的值都加上,就是前缀和了!!不信你可以找几个点蹦一下试试 ,没错就是这么神奇╮(╯_╰)╭……
所以求[L,R]的和,就用[1,R]的和减去[1,L-1]的和就行了~~
//什么玩意,我用数组预处理也能做到嘛好像还O(n)呢,你这O(nlogn)算啥嘛。。。
别急,还有修改呢。。你预处理就没法修改了吧。。。
所以如何修改呢???
单点修改嘛。。看图。。从i点开始不断的加lowbit,直到超出范围为止,单点的维护就搞定了~~ 你可以再找几个点蹦蹦试试 。
具体的原理基本就这样,我自己都觉得讲得不清楚。。
所以还是上代码吧,说不定你们能从代码中洞悉这般奥妙。。
#include <cstdio>
#define gc getchar
class Binary_Tree1{ //树状数组1
public:
inline int getnum() {
int a = 0; char c = gc(); bool f = 0;
for (; (c<'0' || c>'9') && c != '-'; c = gc());
if (c == '-') f = 1, c = gc();
for (; c >= '0'&&c <= '9'; c = gc()) a = (a << 1) + (a << 3) + c - '0';
} //读入优化
inline void putnum(int x) { //快速输出
int c[15], cnt = 0;
for (; x; x /= 10)
c[++cnt] = x % 10;
for (; cnt; cnt--)
putchar(c[cnt] + '0');
}
Binary_Tree1(int n) :n(n) {
} //没任何卵用的构造函数╮(╯_╰)╭
Binary_Tree1() {
}
void Binary_Tree1_Init() {
for (int i = 1; i <= n; i++)
add(i, getnum());
} //读入的初始化
void add(int x, int i) { //单点x加i
for (; x <= n; x += lb(x))
c[x] += i;
}
int sum(int x) { //查前x项的和
int s = 0;
for (; x; x -= lb(x))
s += c[x];
return s;
}
int query(int L, int R){ //区间查[L,R]的和
return sum(R) - sum(L - 1);
}
private:
static const int MAXN = 500005;
inline int lb(int x) { //lb就是lowbit,我嫌字母多就缩写了
return x&(-x);
}
int c[MAXN], n;
};