【数据结构】树状数组

引言

树状数组可以说是最好写,也是最好用的一种数据结构了~

树状数组

概览

树状数组是用数组存储的,它并没有显式的树形结构,而是用数组下表的二进制表示中最低位1的位置构建的一个隐式的树形结构。

这里不盗图了,要有版权意识~所以,更形象的展示可以参考这个博客中的AB图。

关于lowbit

lowbit当然不是low逼的意思啦~

lowbit(x)=x&-x,要理解这个,首先要知道计算机中补码的概念。如+8,因为正数的补码为其本身,所以其的二级制表示为01000(最高位是符号位)。而-8的补码将+8的所有位(包括符号位)取反,然后加1的结果,也就是11000。

所以,这个函数的作用就是求x最低位1所对应的数字,lowbit(8)=1000=8。

操作

树状数组支持单点修改区间查询操作。

其中单点修改是增量修改,区间查询支持具有区间可合并性质的属性。

当然,经过改造也可以进行区间修改,大致思想就是引入一个delta数组,delta[i]表示区间 [i, n] 的共同增量,然后剩下的就不多说了,可以自行推导了~

代码

单点修改+区间查询

1
2
3
4
5
6
7
8
9
void update(int x, int ad) {
for (; x <= n; x += lowbit(x)) c[x] += ad;
}

int getsum(int x) {
int sum = 0;
for (; x ;x -= lowbit(x)) sum += c[x];
return sum;
}

应用

找个比较水的题,poj3321,用树的先序遍历构建成数组,然后在上面跑裸的树状数组就好了~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>

using namespace std;

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

int head[N], nxt[N], to[N];
int c[N];
int cnt, num;
int n, m;
int in[N], out[N];

void update(int x, int ad) {
for (; x <= n; x += lowbit(x)) c[x] += ad;
}

int getsum(int x) {
int sum = 0;
for (; x ;x -= lowbit(x)) sum += c[x];
return sum;
}

void add(int u, int v) {
to[cnt] = v; nxt[cnt] = head[u]; head[u] = cnt++;
}

void dfs(int x) {
in[x] = num;
for (int i = head[x]; ~i; i = nxt[i]) {
update(++num, 1);
dfs(to[i]);
}
out[x] = num;
}

int main() {
memset(head, -1, sizeof head);
memset(nxt, -1, sizeof nxt);
cnt = num = 0;
scanf("%d", &n);
for (int i = 1, a, b; i < n; i++) {
scanf("%d%d", &a, &b);
add(a, b);
}
update(++num, 1);
dfs(1);

scanf("%d", &m);
char str[5];
int x;
getchar();
while (m--) {
scanf("%s%d", str, &x);
if (str[0] == 'Q') {
printf("%d\n", getsum(out[x]) - getsum(in[x] - 1));
}
else {
if (getsum(in[x]) - getsum(in[x] - 1) == 1)
update(in[x], -1);
else
update(in[x], 1);
}
}
return 0;
}

(待更新…)